Files.walkFileTree() 遍历中累加文件大小应在 visitFile() 中用 AtomicLong 调用 attrs.size() 实现,需重写 visitFileFailed() 避免中断,并注意符号链接、稀疏文件、硬链接重复计数及挂载点穿越等影响精度的因素。

Files.walkFileTree() 遍历中途怎么累加文件大小
直接在 visitFile() 里读取并累加 fileSize 是最简路径,但要注意:必须用线程安全的累加方式(比如 AtomicLong),否则多线程遍历时结果错乱;另外别漏掉符号链接——默认 Files.walkFileTree() 不跟随符号链接,如果目标目录含软链指向大文件,它们不会被计入。
实操建议:
- 声明一个
AtomicLong total = new AtomicLong(0)作为累加器 - 在
visitFile(Path file, BasicFileAttributes attrs)中调用attrs.size()(它比Files.size(file)快,且已由 walk 提前读取) - 若需统计符号链接目标文件大小,改用
EnumSet.of(FileVisitOption.FOLLOW_LINKS)启动遍历 - 忽略访问失败的条目(如无权限目录)——在
visitFileFailed()里简单return FileVisitResult.CONTINUE即可,别抛异常中断整个遍历
FileVisitor 实现里哪些方法必须重写
只统计大小的话,preVisitDirectory() 和 postVisitDirectory() 完全可以空实现;真正关键的是 visitFile()——它每遇到一个普通文件就触发一次;而 visitFileFailed() 建议显式重写,否则默认抛出异常会中断遍历。
常见错误现象:没重写 visitFileFailed(),一碰到 AccessDeniedException 就整个流程崩掉,返回值远小于实际大小。
最小可用实现结构:
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
total.addAndGet(attrs.size());
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
// 例如打印日志:System.err.println("skip " + file + ": " + exc.getMessage());
return FileVisitResult.CONTINUE;
}
}
和 Files.list() + Files.walk() 比,walkFileTree 有什么不可替代性
核心优势是控制粒度细、内存占用低、错误恢复强。比如 Files.walk() 返回 Stream,一旦中间某个目录因权限失败,整个流就终止;而 walkFileTree() 允许你在 visitFileFailed() 里决定“跳过这个,继续下一个”。
性能上,walkFileTree() 不构建完整路径列表,边遍历边处理,对超大目录(百万级文件)更稳;但如果你只需要总大小,别在 visitFile() 里做耗时操作(比如计算 MD5),否则拖慢明显。
注意点:
-
Files.walk()默认深度无限制,可能栈溢出;walkFileTree()可通过Integer.MAX_VALUE显式设上限,更可控 -
Files.list()只查一级,没法递归,完全不适用此场景 - Windows 下长路径(>260 字符)可能触发
InvalidPathException,需在visitFileFailed()中捕获并跳过
统计结果不准的三个隐蔽原因
不是代码逻辑错,而是系统/文件系统层面的干扰:
- 稀疏文件(sparse file):
attrs.size()返回的是逻辑大小,但磁盘实际占用可能小得多;du -b类工具才反映真实块占用 - 硬链接重复计数:同一个 inode 被多个路径引用,
walkFileTree()会把每个路径都算一遍——除非你手动缓存attrs.fileKey()去重 - 挂载点穿越:默认
walkFileTree()会跨分区进入其他挂载点(如 /mnt/data),若不想统计,得在preVisitDirectory()里检查Files.isSameFile(dir, Paths.get("/"))或用Files.getFileStore()判定是否同文件系统
硬链接去重和挂载点拦截属于进阶需求,多数场景下直接累加 attrs.size() 就够用;真要精确到物理块占用,得切到 OS 层调用 stat 或 GetDiskFreeSpaceEx,Java 标准库做不到。

















