不能直接用md5.sum读完整大文件,因其会一次性加载全部字节到内存导致oom;必须使用hash.hash接口配合io.copy流式分块计算,内存恒定且保障字节流完整性。

为什么不能直接用 md5.Sum 读完整个大文件?
内存爆掉是大概率事件。比如一个 4GB 的视频文件,md5.Sum 默认会把整个内容加载进内存再哈希,Go runtime 直接 OOM。正确做法是流式分块计算——每次只读固定大小(如 1MB)的 chunk,更新哈希状态,最后取最终值。
- 必须用
hash.Hash接口的Write方法逐块写入,而非一次性传入全部数据 - 推荐用
io.CopyN或io.ReadFull控制每次读取字节数,避免最后一块不足时出错 - 别用
md5.New()后直接io.Copy(h, file)—— 这看似简洁,但若文件中途断开或需要校验中间块,就失去分块粒度控制
如何设计可验证的分块结构?
断点续传的核心不是“能传”,而是“传完能确认每一块没被篡改或截断”。光有整体文件哈希不够,得为每个块单独生成哈希,并存成可索引的结构。
- 块大小建议固定(如
1024 * 1024),最后一块不足也单独算一个哈希,不要补零 - 每个块的哈希存进 slice:
[]struct{ Offset int64; Size int; Hash [16]byte },Offset用于定位、Size用于校验读取长度是否匹配 - 不要把哈希列表和文件混在一起存;应单独生成一个
.meta文件(JSON 或 Protocol Buffers 序列化),否则传输中断后无法判断元数据是否写全
sha256 比 md5 更适合断点续传场景吗?
不是“更适合”,而是“更安全”。md5 在碰撞攻击下已不被信任,而断点续传一旦被中间人篡改某一块,用户可能只重传了恶意块却以为校验通过。
- Go 标准库中
crypto/sha256和crypto/md5的 API 完全一致,替换成本极低 - 性能差异在现代 CPU 上几乎不可测(尤其 I/O 是瓶颈),别因“慢一点”放弃安全性
- 如果服务端强制要求
md5(如某些老协议),至少确保客户端校验时做二次比对:先按服务端规则算 md5,再本地用 sha256 算一遍存档,便于事后审计
断点续传时怎么跳过已校验成功的块?
关键不在“跳过”,而在“可靠判定哪一块已成功”。常见错误是只检查本地文件长度,但长度对不代表内容对。
- 必须重新读取已写入的块(从目标文件
Offset开始读Size字节),用相同哈希算法再算一次,和 meta 中对应项比对 - 别依赖文件系统修改时间或临时文件名——这些都可能被干扰或误判
- 如果校验失败,不要 truncate 后续所有块,而应仅丢弃当前块并重传;否则网络抖动导致单块失败就会引发整段重传
真正麻烦的是并发写入和原子性:多个 goroutine 写同一文件不同 offset 时,要确保 write + fsync 成对出现,且校验必须发生在 fsync 之后。否则可能读到缓存中未落盘的数据,哈希对不上。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











