必须为每个文件调用 md5.New() 创建新哈希实例,因 hash.Hash 非并发安全且不可重置;循环外复用会导致哈希值错误叠加;不可依赖未公开的 Reset 方法;并发时各 goroutine 应独立 new、open、copy;推荐使用 io.Copy(内置 32KB 缓冲及零拷贝优化),避免手动分块读写。

md5.New() 必须每个文件新建,不能复用
hash.Hash 接口不是并发安全的,也不是可重置的。如果在循环里只调一次 md5.New(),然后反复 io.Copy(h, file),第二个文件的哈希值其实是第一个 + 第二个文件内容拼接后的结果——看着没报错,但值完全错。
常见错误场景:批量校验目录下多个 .zip 文件时,把 h := md5.New() 写在 for 循环外。
- 每次处理新文件前,必须调用一次
md5.New() - 别试图用
h.Reset()——hash.Hash接口没这个方法,md5.digest虽有但不公开,不可靠 - 并发场景下更要严格隔离:每个 goroutine 自己 new、自己 open、自己 copy
io.Copy 是首选,别手动分块 Read+Write
io.Copy 内部已用 32KB 缓冲区优化,且对 hash.Hash 做了零拷贝路径适配。手动分块不仅代码冗长,还容易漏掉最后一段不足缓冲区大小的数据(比如你 file.Read(buf) 返回 n 却没写进 hash)。
典型误操作:for { n, _ := file.Read(buf); h.Write(buf[:n]); if n == 0 { break } } —— 这里没检查 err,也没处理 io.EOF 和真实错误的区别。
立即学习“go语言免费学习笔记(深入)”;
- 直接用
io.Copy(h, file),它会自动处理读完、中断、错误传播 - 务必检查
io.Copy的第二个返回值error:如果中途权限不足或磁盘损坏,err != nil但你没判,就会算出部分数据的 MD5 - 不需要
defer h.Close()——hash.Hash没Close方法,也无需关闭
h.Sum(nil) 怎么用才不出错
h.Sum(nil) 返回的是新分配的 []byte,长度 16;而 md5.Sum(data) 返回的是结构体,得用 sum[:] 才能拿到等价切片。混用会导致格式化输出异常或内存污染。
最常踩的坑:fmt.Printf("%x", h.Sum(nil)) 看起来能打印,但实际输出是 xxx...000000(后面多出 16 字节补零),因为 Sum 方法语义是“追加到给定切片”,nil 是安全起点,但直接传给 %x 会把整个目标切片当参数,而非仅哈希值本身。
- 正确转字符串:
hex.EncodeToString(h.Sum(nil))或fmt.Sprintf("%x", h.Sum(nil)[:]) - 别用
h.Sum([]byte{}):底层数组可能被复用,尤其在并发中多个 goroutine 写同一底层数组,结果互相覆盖 - 小文件测试时偶尔“看起来对”,是因为 GC 没及时回收旧切片,大文件或高并发下立刻暴露
Windows 下文件句柄冲突怎么绕过
Windows 常见报错:The process cannot access the file because it is being used by another process。根源不是你的代码,而是资源管理器缩略图生成、杀毒软件实时扫描、OneDrive 同步进程已打开该文件并持有句柄。
用 os.Open 默认行为在 Windows 上无法规避这类竞争,必须显式控制打开标志。
- 改用
os.OpenFile(path, os.O_RDONLY|os.O_CLOEXEC, 0),os.O_CLOEXEC能减少子进程继承句柄导致的干扰 - 捕获特定错误:
os.IsPermission(err)或strings.Contains(err.Error(), "being used by another process") - 加最多 3 次重试,每次
time.Sleep(100 * time.Millisecond),避免瞬时冲突失败 - 校验前先
os.Stat确认是普通文件,跳过目录、设备文件、符号链接,防止逻辑误入死循环
真正卡住人的从来不是算法本身,而是流式读取过程中那个没检查的 io.Copy 错误,或是 Windows 下一句没加 os.O_CLOEXEC 的 OpenFile。


















