必须用filepath.walk递归遍历目录,因os.readdir仅单层且手动递归易出错;需转绝对路径、过滤非普通文件、妥善处理权限错误、分块计算md5、控制并发并规范输出格式。

用 filepath.Walk 遍历目录,别用 os.ReadDir 简单循环
递归获取所有文件(含子目录)必须用 filepath.Walk,os.ReadDir 只读一层,容易漏掉嵌套路径。尤其当目录深度不确定时,手动实现递归易出错且难维护。
注意:传给 filepath.Walk 的路径要确保是绝对路径或已处理好相对路径,否则遇到符号链接或跨挂载点时可能行为异常(比如跳过、panic 或重复遍历)。
- 用
filepath.Abs统一转成绝对路径再 walk,避免相对路径导致的 cwd 依赖 - 在
WalkFunc回调里用fi.Mode().IsRegular()过滤非普通文件(跳过目录、设备、socket 等) - 遇到权限错误(如
os.ErrPermission)应记录但不中断遍历,否则可能丢失大量有效文件
计算 MD5 时按块读取,禁止全量加载到内存
大文件(比如几百 MB 的日志或镜像)直接 os.ReadFile 会爆内存,且阻塞 goroutine。必须用 io.Copy 或 hash.Hash.Write 分块处理。
Go 标准库的 crypto/md5 实现支持流式写入,只要打开文件句柄后边读边哈希即可,无需缓存全文本。
- 每次读取建议用
make([]byte, 32*1024)(32KB)缓冲区,平衡系统调用开销和内存占用 - 务必检查
file.Stat()获取大小,若为 0 字节可跳过读取,直接算空内容的 MD5(md5.Sum([]byte{})) - 别忽略
defer file.Close()—— 多文件下 fd 耗尽会导致后续 open 失败(too many open files)
并发控制不当会导致 I/O 打满或 panic
直接对每个文件起 goroutine 跑 MD5,看似快,实则容易压垮磁盘或触发 runtime: goroutine stack exceeds 1GB limit(尤其小文件极多时)。
更稳的方式是固定 worker 数量 + channel 控制任务分发,而不是无限制 spawn。
- worker 数量建议设为
runtime.NumCPU() * 2,SSD 上可略高,HDD 上建议 ≤4 - 用带缓冲的
chan *os.FileInfo分发文件元信息,worker 再自行打开并计算,避免提前打开太多 fd - 不要把整个文件内容塞进 channel —— 只传路径或
*os.FileInfo,让 worker 自行读取,减少内存拷贝
输出格式需兼顾可解析性与人类可读性
纯打印到终端容易被截断或难以下游处理;只输出 JSON 又不方便快速 eyeball 检查。折中做法是默认输出 tab 分隔的文本,加 -json flag 切换格式。
字段顺序建议固定为:md5sum\tsize\trelpath,这样可用 sort -k1,1 快速去重或比对。
- 相对路径用
filepath.Rel(root, absPath)计算,避免输出冗长绝对路径 - MD5 值统一用小写十六进制字符串(
fmt.Sprintf("%x", sum)),别用%X或 base64 - 遇到无法读取的文件,至少输出一行
"<error>\t0\t<relpath>"</relpath></error>并记录 stderr,方便定位问题
实际跑起来最常卡在权限错误没处理、大文件 OOM、或并发数设太高导致磁盘寻道延迟飙升——这些都不是算法问题,而是 IO 工程细节。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











