最稳妥方案是用 filepath.Walk 并正确处理错误回调和目录判断;os.ReadDir 仅适用于单层遍历,手动递归易出错;并发无益于提速,I/O 才是瓶颈;symlink 需用 WalkDir 防循环。

用 filepath.Walk 遍历目录并计数最稳妥
直接用 os.ReadDir(Go 1.16+)只能读单层,统计子目录下所有文件必须递归。虽然 filepath.Walk 稍慢一点,但它能自动处理符号链接、权限错误等边界情况,比手写递归更少出错。
常见错误是忽略 filepath.Walk 的错误回调:比如遇到无权限目录时,不返回 nil 就会中断遍历;或者把 os.FileInfo.IsDir() 判定逻辑放反,把目录当文件计数。
- 始终在 walk 函数中检查
err != nil,对os.ErrPermission返回nil继续遍历 - 只对
!fi.IsDir()的项递增计数器,别漏掉隐藏文件(.开头的也算普通文件) - 计数器变量必须是指针或闭包外的可变变量,不能在 walk 函数里声明新变量
想快一点?用 runtime.GOMAXPROCS 并行没用,别白费劲
文件系统 I/O 是瓶颈,不是 CPU。强行开 goroutine 并发调 filepath.Walk 不仅不会提速,反而因系统调用竞争和锁争用拖慢整体速度,还可能触发大量 too many open files 错误。
真正影响速度的是磁盘类型(HDD vs SSD)、文件数量级和路径深度。实测在 10 万小文件场景下,单 goroutine 的 filepath.Walk 比 4 个并发的自定义 walker 快 15%~20%。
- 不要用
sync.WaitGroup+ 多个filepath.Walk分段扫描——路径无法干净切分 -
os.ReadDir+ 手动队列 BFS 在某些 SSD 上略快,但代码复杂度高,且对 symlink 循环无保护 - 如果真要加速,优先考虑用
find /path -type f | wc -l调用外部命令(仅限可信环境)
os.ReadDir 适合只统计当前目录,别硬套递归
很多人看到 Go 1.16 引入了 os.ReadDir 就以为它能替代 filepath.Walk,其实它只是 Readdir 的零分配优化版,不递归。硬用它写递归容易写出栈溢出或死循环(比如没过滤 . 和 ..)。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
典型错误是在递归函数里忘记检查 err:比如某次 os.ReadDir(subdir) 失败(权限/不存在),却继续 for 循环,导致 panic。
- 用
os.ReadDir时,显式跳过entry.Name() == "." || entry.Name() == ".." - 每次调用后必须检查
err,非nil就 return,别试图“忽略” - 只在明确知道目录层级浅、且不需要处理 symlink 循环时才考虑它
注意 filepath.Walk 对 symlink 的默认行为
默认情况下,filepath.Walk 会跟随符号链接,如果目标是目录,就会重复进入(甚至形成循环)。这不是 bug,是设计如此——但会导致重复计数或无限遍历。
如果你的目录树里有 symlink(尤其是指向父目录或自身),必须用 filepath.WalkDir(Go 1.16+)配合 fs.DirEntry 手动控制是否跟随。
-
filepath.WalkDir的 walkFn 第二个参数是fs.DirEntry,可用entry.Type() & fs.ModeSymlink != 0判断 - 检测到 symlink 后,用
entry.Info()获取真实路径,再用filepath.EvalSymlinks防循环 - 若无需处理 symlink,最简单方案是加个已访问路径 map,用
filepath.Clean标准化后判重
实际统计逻辑跑起来很快,但绕不开路径解析和 syscall 开销。真正卡住的往往不是代码写法,而是没意识到某些目录(如 /proc 或容器挂载点)会动态生成成千上万个伪文件——这种场景得加超时或白名单过滤,否则数分钟都出不来结果。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










