os.ReadDir单层+filepath.WalkDir递归是当前最稳最快组合,因前者零系统调用获取名称与类型,后者避免隐式os.Stat且支持SkipDir;filepath.Walk和ioutil.ReadDir则因重复系统调用或已弃用应淘汰。

用 os.ReadDir 扫单层目录、filepath.WalkDir 做递归,是当前最稳最快的组合;别再用 filepath.Walk 或 ioutil.ReadDir,它们要么隐式调 os.Stat 拖慢速度,要么已被弃用。
为什么 os.ReadDir 比 os.Stat 快得多
os.ReadDir 返回的是 fs.DirEntry 切片,entry.Name() 和 entry.IsDir() 是零系统调用操作——名字来自 readdir 结果,类型字段(d_type)在 ext4/xfs 等文件系统上直接可用。而 os.Stat 每次都触发一次 stat(2) 系统调用,读 inode 元数据。
- 遍历 10 万个文件时,全量调
entry.Info()可能比只用Name()+IsDir()慢 3–5 倍 -
entry.Type()在不支持d_type的文件系统(如某些 NFS 或 FAT32)会 fallback 到entry.Info(),悄悄引入开销甚至 panic - 要取大小或修改时间?只对目标文件调
entry.Info(),别无脑调用
filepath.WalkDir 回调里怎么安全拿元数据
filepath.WalkDir 的回调函数签名为 func(path string, d fs.DirEntry, err error) error,它不自动调 os.Stat,但你仍得小心使用 d.Info()。
- 如果只判断是否为普通文件,用
!d.IsDir()就够了,别再调d.Info().Mode().IsRegular() - 需要文件大小?只对
!d.IsDir()的项调d.Info(),并检查返回的err(权限不足时可能失败) - 符号链接默认不跟随,
d.Type() & fs.ModeSymlink != 0可识别,但解析需额外调os.Readlink,注意循环引用 - 遇到
err != nil(如fs.ErrPermission),应显式return filepath.SkipDir继续扫描兄弟路径,而非忽略
并发扫描时最容易爆掉的三个资源点
并发不是开越多 goroutine 越快,磁盘 I/O 的瓶颈不在 CPU,而在 fd 数、内核调度和缓冲区竞争。
立即学习“go语言免费学习笔记(深入)”;
-
too many open files:每个os.ReadDir打开一个目录 fd,不限流会迅速触达ulimit -n;用带缓冲 channel 控制并发数,例如sem := make(chan struct{}, 8) - goroutine 泄漏:别为每个文件启 goroutine;应为每个子树根目录启一个,处理完即退
- 误调
runtime.GOMAXPROCS:I/O 密集型任务调高只会加剧调度抖动;保持默认值(通常等于 CPU 核心数)更稳
真正难的不是写对逻辑,而是控制好 entry.Info() 的调用时机——它看着轻量,实则一不小心就退回 filepath.Walk 级别的性能泥潭。


















