files.walk() 是 java 8+ 递归遍历目录最稳方案,支持深度优先、自动跳过权限不足路径,返回 stream 便于过滤统计;需注意关闭流、过滤非普通文件、限制数量防 oom、捕获异常并判断真实可达性。

用 Files.walk() 递归遍历目录最稳
Java 8+ 的 Files.walk() 是处理目录递归的首选,它天然支持深度优先、自动跳过权限不足路径,且返回的是 Stream<path></path>,和后续过滤统计能自然衔接。别手写 File.listFiles() 递归——容易漏掉符号链接、抛 NullPointerException 或陷入无限循环(比如软链成环)。
实操建议:
- 加
.onClose(() -> System.out.println("遍历完成"))方便调试是否真正结束(流不消费完会不触发关闭) - 用
Files.isRegularFile(path)过滤掉目录、设备文件等非普通文件 - 对大目录加
.limit(10000)预防 OOM(尤其 Docker 容器里内存有限) - 捕获
IOException并用Files.exists(path, LinkOption.NOFOLLOW_LINKS)判断是否真不可达,避免因权限问题中断整个流
用 Pattern.compile() 做后缀和内容双过滤
只按扩展名过滤(比如 .java)不够——项目里常混着 build.gradle、pom.xml、甚至 README.md 里夹带代码块。得用正则同时筛路径和内容:前者控范围,后者保质量。
常见错误现象:path.toString().endsWith(".java") 会漏掉 /src/main/java/com/example/Util.java(Windows 路径分隔符是 ),或误吞 MyClass.java.bak。
立即学习“Java免费学习笔记(深入)”;
实操建议:
- 路径过滤用
Pattern.compile(".*\.java$").matcher(path.toString()).find(),锚定结尾,兼容各种分隔符 - 内容过滤建议读取前 2KB 就够(
Files.lines(path).limit(100).filter(...)),避免大文件卡住;跳过空行、纯注释行用line.trim().isEmpty() || line.trim().startsWith("//") || line.trim().startsWith("/*") - 注意
Files.lines()默认 UTF-8,若项目含 GBK 编码的遗留文件,必须显式传StandardCharsets.GBK,否则中文行会报MalformedInputException
Files.lines() 统计时小心编码与空行逻辑
直接 Files.lines(path).count() 看似省事,但默认把空行、纯注释、花括号独占行全算进去,和 IDE 里“有效代码行”概念差很远。更麻烦的是,没指定编码时遇到非 UTF-8 文件直接炸。
使用场景:CI 流水线里跑行数报告,要求结果稳定可比;或者审计第三方 SDK 源码规模。
实操建议:
- 用
Files.lines(path, StandardCharsets.UTF_8)显式声明编码,宁可失败也不静默乱码 - 有效行判断别只靠
!line.trim().isEmpty()——要排除{、}单独成行的情况,加!line.trim().matches("[{}\s]*") - 统计完立刻
.close()(或用 try-with-resources),否则流不关会导致文件句柄泄漏,在 Linux 上跑几百个文件就可能触发Too many open files
性能瓶颈通常卡在 I/O,不是正则
很多人一上来优化正则,其实磁盘读比字符串匹配慢两个数量级。一个 500 行的 .java 文件,解析耗时约 0.2ms,而从 SSD 读它要 0.8ms,HDD 更是 3~5ms。正则再复杂,也压不过系统调用开销。
参数差异:用 BufferedReader 手动读比 Files.lines() 快 10%~15%,但代码量翻倍;用 Files.readAllLines() 内存占用高但适合小文件(
实操建议:
- 对单文件统计,优先用
Files.readAllLines(path, cs)+for (String line : lines),避免 Stream 创建开销 - 并发统计多个文件?别直接
.parallel()——I/O 密集型任务并行反而降低吞吐,用固定大小线程池(如Executors.newFixedThreadPool(4))更稳 - 如果目标只是粗略估算(比如“这个模块大概两千行”),跳过内容扫描,只用
Files.lines(path).count()+ 后缀过滤,速度能快 5 倍
真正难的不是写出来,是搞清你要的“行数”到底指什么:IDE 显示的?SonarQube 认的?还是老板嘴里“一个人周能写多少行”的那个行?定义模糊,工具再准也没用。










