Go中字符串哈希优先用md5.Sum([]byte(s))或sha256.Sum256([]byte(s)),类型安全、零状态、开销最小;文件哈希必须用os.Open+sha256.New()+io.Copy流式处理,避免OOM。

crypto/md5 和 crypto/sha256 是 Go 标准库中实际可用、无需额外依赖的哈希方案,但它们的用法差异大、易踩坑——尤其是混淆 Sum() 与 New() 的适用场景。
字符串哈希该用 md5.Sum() 还是 sha256.Sum256()?
对纯字符串做一次性哈希,优先用 Sum() 类函数:md5.Sum([]byte(s)) 或 sha256.Sum256([]byte(s))。它们返回固定长度数组([16]byte 或 [32]byte),类型安全、无状态、开销最小。
常见错误是写成:
hash := md5.New()
hash.Write([]byte(s))
fmt.Printf("%x", hash.Sum(nil)) // ✅ 可行但冗余
// vs
fmt.Printf("%x", md5.Sum([]byte(s))) // ✅ 更简洁,推荐
-
Sum()和Sum256()是值语义,不维护内部状态,适合单次计算 - 别对同一个
Sum256实例反复调用Sum256(data)期望增量效果——它每次都是全新计算 - 输出哈希串时,
fmt.Sprintf("%x", h)比hex.EncodeToString(h[:])少一次切片转换,更轻量
文件哈希必须用 os.Open + sha256.New() + io.Copy
任何大小未知的文件,禁止用 os.ReadFile 加载全量内容再喂给 Sum256 ——哪怕只是 50MB,在内存受限环境(如容器、CI)里就可能触发 OOM。
唯一安全路径是流式处理:
立即学习“go语言免费学习笔记(深入)”;
file, err := os.Open("data.bin")
if err != nil { return "", err }
defer file.Close() // ⚠️ 必须 defer,否则 fd 泄露
h := sha256.New()
_, err = io.Copy(h, file) // 自动分块(默认 32KB),内存恒定
if err != nil { return "", err } // ⚠️ 必须检查 io.Copy 的 error
return fmt.Sprintf("%x", h.Sum(nil)), nil
-
io.Copy内部会处理读取错误(磁盘满、NFS 中断、权限不足),忽略等于静默失败 -
sha256.Sum256不能接收*os.File,也不能“流式”写入——它只接受字节切片,本质是快捷封装 - 机械硬盘上可加
bufio.NewReader(file)提升小块读稳定性;SSD 上基本无收益
Sum(nil) 返回 []byte,Sum256() 返回 [32]byte,别混用
这两者类型不兼容,强行赋值会编译失败。比如:
var s sha256.Sum256
s = sha256.Sum256([]byte("a")) // ✅ ok
b := s[:] // ✅ 转切片
// vs
h := sha256.New()
h.Write([]byte("a"))
b := h.Sum(nil) // ✅ []byte,可直接 hex.EncodeToString 或 fmt.Sprintf("%x", b)
-
h.Sum(nil)返回的是新分配的[]byte,内容拷贝自内部状态 -
s[:] → []byte是切片视图,零拷贝,但仅限Sum256这类值类型 - 别写
fmt.Println(h.Sum(nil))——输出是{[123 45 ...]}这种结构体字面量,不是哈希串
空文件、路径不存在、权限错误必须显式处理
os.Open 失败在生产环境极其常见:容器里路径未挂载、CI 中文件未生成、Linux 上权限被 umask 限制……但很多人只写 if err != nil { panic(...) } 或干脆忽略。
- 空文件(
0 byte)能正常算出哈希,io.Copy返回(0, nil),没问题 - 路径不存在或权限不足,
os.Open直接返回 error,必须拦截并返回有意义的错误(比如os.IsNotExist(err)) - 别依赖
io.Copy后再检查file.Stat()——顺序错,且 Stat 可能因并发删文件而失败


















