直接比较大文件内容不现实,因逐字节比对耗时且占内存;应使用sha256流式计算32字节哈希摘要再比对,并辅以首尾64kb二次校验确保准确性。

为什么用哈希而不是直接比较文件内容
直接读取并逐字节比对两个大文件,IO 和 CPU 开销都高,尤其在海量小文件场景下,频繁 ReadAll 会触发大量内存分配和 GC。哈希是空间换时间:一次计算 + 固定长度摘要(如 sha256.Sum256),后续只需比对 32 字节,速度提升一个数量级。
但要注意:哈希碰撞概率极低,但不为零;生产环境若需 100% 确保相同,应在哈希匹配后加一层 bytes.Equal 或 os.SameFile(仅限同一挂载点)校验——不过绝大多数重复检测场景,SHA-256 已足够可靠。
如何避免内存爆掉:流式哈希 + 复用哈希器
对几百 MB 的文件调用 ioutil.ReadFile 或 os.ReadFile 容易 OOM。必须用 io.Copy 流式写入哈希器,且复用 hash.Hash 实例(如 sha256.New() 返回的指针)。
- 每次计算前调用
h.Reset()清空状态,别新建实例 - 打开文件后立即 defer
file.Close(),防止句柄泄漏 - 用
bufio.NewReader(file)包裹可略微提升小文件吞吐,但对大文件影响不大
示例关键片段:
h := sha256.New() file, _ := os.Open(path) defer file.Close() io.Copy(h, file) sum := h.Sum(nil) // 注意:sum 是 []byte,不是指针
如何高效处理目录树并去重
filepath.WalkDir 比 filepath.Walk 更轻量,且能跳过子目录(比如遇到 .git 直接 return filepath.SkipDir)。但真正影响性能的是哈希并发控制——无限制 goroutine 启动会导致系统打开太多文件句柄或调度抖动。
- 用带缓冲的 channel 控制并发数(如 8–16 个 worker),避免
runtime: failed to create new OS thread - 哈希结果用
map[[32]byte][]string存储,key 是sha256.Sum256的数组形式([32]byte可作 map key,而[]byte不行) - 路径收集建议用
append切片,而非反复+=字符串拼接
注意硬链接与符号链接的语义差异
默认 os.Stat 跟符号链接,os.Lstat 不跟。重复检测中,你通常想把硬链接视为同一文件(os.SameFile(a, b) 返回 true),但符号链接指向不同内容时应独立哈希。
- 若需跳过符号链接,遍历中检查
fi.Mode() & os.ModeSymlink != 0,然后continue - 硬链接天然共享 inode,
os.Stat后对比sys.(*syscall.Stat_t).Ino可提前判等,省去哈希计算——但跨设备无效,慎用 - Windows 上硬链接少见,且
os.SameFile在 NTFS 下行为稳定,但 FAT32 不支持,需 fallback 到哈希
实际跑起来最慢的环节往往是磁盘随机读,而不是哈希计算本身;如果发现瓶颈在 IO,说明你可能漏掉了并发控制或没用 SSD。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











