必须用 os.open + io.copy 流式计算 sha256,禁用 os.readfile 大文件;每个文件独立调用 sha256.new();校验用 bytes.equal 比较原始字节数组,非字符串;空文件需提前检查;defer f.close() 防 fd 泄漏。

Go 标准库的 crypto/sha256 是唯一推荐路径,不用第三方包,不踩内存泄漏、状态复用、字符串误比这些坑,就能稳稳校验文件完整性。
为什么不能用 os.ReadFile 读大文件再哈希
大文件(比如几百 MB 或 GB)直接 os.ReadFile 会把全部内容加载进内存,程序大概率 OOM 崩溃,尤其在容器或低配机器上。这不是“可能慢”,是“必然挂”。
- 必须用
os.Open打开句柄,配合io.Copy流式写入哈希对象——hash.Hash实现了io.Writer,天然支持 - 别自己写
for循环 +Read,容易漏掉最后一块不足缓冲区的数据 - 加一层
bufio.NewReader(f)能提升机械盘小文件读取效率,SSD 上收益不大但无害
sha256.New() 必须每次新建,不能复用同一个实例
复用一个 sha256.New() 实例连续算两个文件,第二个结果其实是第一个 + 第二个文件内容拼起来的哈希,完全错误。
- 每个文件都调用一次
sha256.New(),哪怕在循环里也得这么干 - 并发场景下更要确保每个 goroutine 持有独立的 hash 实例,
hash.Hash不是线程安全的 - 别用
sha256.Sum256{}零值结构体直接调Sum(nil),那返回的是空哈希(全零),不是文件哈希
校验时别用字符串比较,用 bytes.Equal
把本地算出的哈希转成十六进制字符串,再跟服务端给的字符串比对,看着简单,其实埋了两个雷:一是额外分配和 GC 压力;二是如果服务端字符串被篡改(比如多了空格、换行、大小写混用),字符串比较就失效了。
- 服务端给的哈希如果是 hex 字符串(如
"a4f87b2c..."),先用hex.DecodeString解码成[]byte,记得检查err - 本地计算完用
h.Sum(nil)[:]拿到原始字节数组,直接bytes.Equal(local, remote) - 如果是 Content-Security-Policy 里的 hash,要用
base64.StdEncoding.DecodeString,不是 hex
空文件和错误处理不能跳过
空文件会得到确定哈希值(SHA256 是 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855),但业务上往往需要单独判断——比如下载后发现文件大小为 0,那根本不用算哈希,直接报错更合理。
- 算哈希前先
os.Stat().Size(),为 0 就提前返回错误或按策略处理 -
io.Copy的返回值 err 必须检查:磁盘满、权限不足、网络中断(如果是 pipe 或 fuse 文件)都会在这里暴露 - 打开文件后立刻
defer f.Close(),否则 fd 泄漏在长时间运行服务中会积少成多
最易被忽略的点是:哈希对象的状态不可重置,Sum(nil) 不会清空内部缓冲,也不代表可以继续写入新数据——它只是把当前状态快照出来。所以“算完一个再算下一个”这件事,本质上只能靠新建实例来保证正确性,没有取巧办法。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











