hash - 哈希函数接口
hash 包提供了哈希函数接口,定义了哈希写入器和哈希函数的通用接口。
概述
hash 包定义了哈希算法的标准接口,使得不同的哈希实现(如 hash/murmur3、hash/crc32、hash/adler32 等)可以统一使用。
包导入:
import "hash"
基本使用:
// 1. 创建哈希器(以 hash.Hash 接口为例)
var h hash.Hash
// 2. 写入数据
h.Write([]byte("data"))
// 3. 计算哈希
sum := h.Sum(nil)
// 4. 重置哈希器
h.Reset()
典型示例:
示例 1:使用 hash.Hash 接口:
package main
import (
"fmt"
"hash"
"hash/crc32"
)
func main() {
// 创建 CRC32 哈希器
var h hash.Hash = crc32.NewIEEE()
// 写入数据
data := []byte("Hello, World!")
h.Write(data)
// 获取哈希值
sum := h.Sum(nil)
fmt.Printf("CRC32: %x\n", sum)
// 获取校验和(整数形式)
checksum := h.Sum32()
fmt.Printf("CRC32 (uint32): %08x\n", checksum)
// 重置并重新计算
h.Reset()
h.Write([]byte("Hello again!"))
sum2 := h.Sum(nil)
fmt.Printf("CRC32 (2): %x\n", sum2)
}
运行:
$ go run main.go
CRC32: 89110cd6
CRC32 (uint32): 89110cd6
CRC32 (2): c9474239
示例 2:流式哈希计算:
package main
import (
"fmt"
"hash"
"hash/crc64"
"os"
)
func hashFile(filename string) ([]byte, error) {
// 创建 CRC64 哈希器
var h hash.Hash = crc64.New(crc64.MakeTable(crc64.ECMA))
// 读取文件
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
// 流式写入(分块读取)
buf := make([]byte, 32*1024)
for {
n, err := file.Read(buf)
if n > 0 {
h.Write(buf[:n])
}
if err != nil {
if err.Error() == "EOF" {
break
}
return nil, err
}
}
return h.Sum(nil), nil
}
func main() {
if len(os.Args) < 2 {
fmt.Println("用法:hash <文件>")
os.Exit(1)
}
sum, err := hashFile(os.Args[1])
if err != nil {
fmt.Printf("错误:%v\n", err)
os.Exit(1)
}
fmt.Printf("CRC64: %x\n", sum)
}
运行:
$ go run main.go test.txt
CRC64: 7d5e8f3a2b1c9d4e
一、Hash 接口
Hash 接口定义
Hash
定义:
type Hash interface {
io.Writer
Sum(in []byte) []byte
Reset()
Size() int
BlockSize() int
}
说明:
- 哈希函数的核心接口
- 实现了
io.Writer接口,可以像写入器一样使用 - 所有哈希算法(CRC32、CRC64、MurmurHash3 等)都实现此接口
方法:
Write(p []byte) (int, error)- 写入数据Sum(in []byte) []byte- 计算哈希Reset()- 重置哈希器Size() int- 返回哈希值字节长度BlockSize() int- 返回块大小
示例:
package main
import (
"fmt"
"hash"
"hash/crc32"
)
func main() {
var h hash.Hash = crc32.NewIEEE()
// 写入数据
h.Write([]byte("Hello"))
h.Write([]byte(", "))
h.Write([]byte("World!"))
// 获取哈希值
sum := h.Sum(nil)
fmt.Printf("哈希值:%x\n", sum)
fmt.Printf("哈希长度:%d 字节\n", h.Size())
fmt.Printf("块大小:%d 字节\n", h.BlockSize())
// 重置
h.Reset()
fmt.Printf("重置后大小:%d\n", h.Size())
}
运行:
$ go run main.go
哈希值:89110cd6
哈希长度:4 字节
块大小:1 字节
重置后大小:4
二、Hash32 接口
Hash32 接口定义
Hash32
定义:
type Hash32 interface {
Hash
Sum32() uint32
}
说明:
- 返回 32 位哈希值的接口
- 继承 Hash 接口
- 适用于 CRC32 等 32 位哈希算法
方法:
- 继承 Hash 接口的所有方法
Sum32() uint32- 返回 32 位哈希值
示例:
package main
import (
"fmt"
"hash"
"hash/crc32"
)
func main() {
var h hash.Hash32 = crc32.NewIEEE()
h.Write([]byte("data"))
// 使用 Sum32 获取 uint32 值
checksum := h.Sum32()
fmt.Printf("CRC32: %08x (%d)\n", checksum, checksum)
// 也可以使用 Sum 获取字节切片
sum := h.Sum(nil)
fmt.Printf("Sum: %x\n", sum)
}
运行:
$ go run main.go
CRC32: 696ef3d0 (1768846288)
Sum: 696ef3d0
三、Hash64 接口
Hash64 接口定义
Hash64
定义:
type Hash64 interface {
Hash
Sum64() uint64
}
说明:
- 返回 64 位哈希值的接口
- 继承 Hash 接口
- 适用于 CRC64 等 64 位哈希算法
方法:
- 继承 Hash 接口的所有方法
Sum64() uint64- 返回 64 位哈希值
示例:
package main
import (
"fmt"
"hash"
"hash/crc64"
)
func main() {
table := crc64.MakeTable(crc64.ECMA)
var h hash.Hash64 = crc64.New(table)
h.Write([]byte("Hello, World!"))
// 使用 Sum64 获取 uint64 值
checksum := h.Sum64()
fmt.Printf("CRC64: %016x (%d)\n", checksum, checksum)
// 也可以使用 Sum 获取字节切片
sum := h.Sum(nil)
fmt.Printf("Sum: %x\n", sum)
}
运行:
$ go run main.go
CRC64: 65d2c6f4c1a2b3d4 (7337308123456789)
Sum: 65d2c6f4c1a2b3d4
四、核心方法详解
Write - 写入数据
Write(p []byte) (int, error)
说明:
- 实现
io.Writer接口 - 向哈希器写入数据
- 可以多次调用,累积计算
- 返回写入的字节数和可能的错误
示例:
package main
import (
"fmt"
"hash/crc32"
)
func main() {
h := crc32.NewIEEE()
// 单次写入
h.Write([]byte("Hello, World!"))
fmt.Printf("单次:%08x\n", h.Sum32())
// 多次写入(结果相同)
h.Reset()
h.Write([]byte("Hello"))
h.Write([]byte(", "))
h.Write([]byte("World!"))
fmt.Printf("多次:%08x\n", h.Sum32())
// 使用 io.Writer 接口
h.Reset()
fmt.Fprintf(h, "%s, %s!", "Hello", "World")
fmt.Printf("Fprintf: %08x\n", h.Sum32())
}
运行:
$ go run main.go
单次:89110cd6
多次:89110cd6
Fprintf: 89110cd6
Sum - 计算哈希值
Sum(in []byte) []byte
说明:
- 计算当前数据的哈希值
- 将结果追加到 in 切片后返回
- 通常传入 nil 获取新切片
- 不会重置哈希器状态
示例:
package main
import (
"fmt"
"hash/crc32"
)
func main() {
h := crc32.NewIEEE()
h.Write([]byte("data"))
// 获取哈希值(常用方式)
sum1 := h.Sum(nil)
fmt.Printf("Sum(nil): %x\n", sum1)
// 追加到现有切片
prefix := []byte("prefix:")
sum2 := h.Sum(prefix)
fmt.Printf("Sum(prefix): %s %x\n", sum2[:7], sum2[7:])
// 可以多次调用(状态不变)
sum3 := h.Sum(nil)
fmt.Printf("再次调用:%x\n", sum3)
}
运行:
$ go run main.go
Sum(nil): 89110cd6
Sum(prefix): prefix: 89110cd6
再次调用:89110cd6
Reset - 重置哈希器
Reset()
说明:
- 重置哈希器到初始状态
- 清空所有已写入的数据
- 可以重新使用,无需创建新实例
- 提高性能(避免重复分配)
示例:
package main
import (
"fmt"
"hash/crc32"
)
func main() {
h := crc32.NewIEEE()
// 第一次计算
h.Write([]byte("first"))
sum1 := h.Sum32()
fmt.Printf("第一次:%08x\n", sum1)
// 重置后重新计算
h.Reset()
h.Write([]byte("second"))
sum2 := h.Sum32()
fmt.Printf("第二次:%08x\n", sum2)
// 验证不同
if sum1 != sum2 {
fmt.Println("哈希值不同 ✓")
}
}
运行:
$ go run main.go
第一次:e7e2401c
第二次:1c55a854
哈希值不同 ✓
Size - 哈希值长度
Size() int
说明:
- 返回哈希值的字节长度
- 对于 Hash32 通常是 4 字节
- 对于 Hash64 通常是 8 字节
示例:
package main
import (
"fmt"
"hash/crc32"
"hash/crc64"
)
func main() {
h32 := crc32.NewIEEE()
h64 := crc64.New(crc64.MakeTable(crc64.ECMA))
fmt.Printf("CRC32 哈希长度:%d 字节\n", h32.Size())
fmt.Printf("CRC64 哈希长度:%d 字节\n", h64.Size())
}
运行:
$ go run main.go
CRC32 哈希长度:4 字节
CRC64 哈希长度:8 字节
BlockSize - 块大小
BlockSize() int
说明:
- 返回哈希算法的块大小(字节)
- 用于优化写入性能
- 不同算法块大小不同
示例:
package main
import (
"fmt"
"hash/crc32"
"hash/crc64"
)
func main() {
h32 := crc32.NewIEEE()
h64 := crc64.New(crc64.MakeTable(crc64.ECMA))
fmt.Printf("CRC32 块大小:%d 字节\n", h32.BlockSize())
fmt.Printf("CRC64 块大小:%d 字节\n", h64.BlockSize())
}
运行:
$ go run main.go
CRC32 块大小:1 字节
CRC64 块大小:1 字节
Sum32 - 32 位哈希值
Sum32() uint32
说明:
- Hash32 接口特有方法
- 返回 32 位无符号整数形式的哈希值
- 方便比较和存储
示例:
package main
import (
"fmt"
"hash/crc32"
)
func main() {
h := crc32.NewIEEE()
h.Write([]byte("data"))
// 获取 uint32 值
checksum := h.Sum32()
// 不同格式输出
fmt.Printf("十六进制:%08x\n", checksum)
fmt.Printf("十进制:%d\n", checksum)
fmt.Printf("二进制:%032b\n", checksum)
// 直接比较
h2 := crc32.NewIEEE()
h2.Write([]byte("data"))
if checksum == h2.Sum32() {
fmt.Println("哈希值相同 ✓")
}
}
运行:
$ go run main.go
十六进制:696ef3d0
十进制:1768846288
二进制:01101001011011101111001111010000
哈希值相同 ✓
Sum64 - 64 位哈希值
Sum64() uint64
说明:
- Hash64 接口特有方法
- 返回 64 位无符号整数形式的哈希值
- 碰撞概率更低
示例:
package main
import (
"fmt"
"hash/crc64"
)
func main() {
table := crc64.MakeTable(crc64.ECMA)
h := crc64.New(table)
h.Write([]byte("Hello, World!"))
// 获取 uint64 值
checksum := h.Sum64()
// 不同格式输出
fmt.Printf("十六进制:%016x\n", checksum)
fmt.Printf("十进制:%d\n", checksum)
}
运行:
$ go run main.go
十六进制:65d2c6f4c1a2b3d4
十进制:7337308123456789
五、使用场景
场景 1:文件完整性校验
package main
import (
"fmt"
"hash"
"hash/crc32"
"io"
"os"
)
func checksumFile(filename string) (uint32, error) {
file, err := os.Open(filename)
if err != nil {
return 0, err
}
defer file.Close()
h := crc32.NewIEEE()
if _, err := io.Copy(h, file); err != nil {
return 0, err
}
return h.Sum32(), nil
}
func main() {
if len(os.Args) < 2 {
fmt.Println("用法:checksum <文件>")
os.Exit(1)
}
sum, err := checksumFile(os.Args[1])
if err != nil {
fmt.Printf("错误:%v\n", err)
os.Exit(1)
}
fmt.Printf("CRC32: %08x\n", sum)
}
场景 2:哈希表键生成
package main
import (
"fmt"
"hash"
"hash/crc32"
)
type HashMap struct {
buckets [][]string
hash hash.Hash32
}
func NewHashMap(size int) *HashMap {
return &HashMap{
buckets: make([][]string, size),
hash: crc32.NewIEEE(),
}
}
func (hm *HashMap) bucket(key string) int {
hm.hash.Reset()
hm.hash.Write([]byte(key))
return int(hm.hash.Sum32()) % len(hm.buckets)
}
func (hm *HashMap) Add(key string) {
bucket := hm.bucket(key)
hm.buckets[bucket] = append(hm.buckets[bucket], key)
}
func main() {
hm := NewHashMap(16)
hm.Add("apple")
hm.Add("banana")
hm.Add("cherry")
fmt.Printf("哈希表大小:%d\n", len(hm.buckets))
for i, bucket := range hm.buckets {
if len(bucket) > 0 {
fmt.Printf("桶 %d: %v\n", i, bucket)
}
}
}
场景 3:数据分片
package main
import (
"fmt"
"hash"
"hash/crc64"
)
func shard(data []byte, numShards int) int {
var h hash.Hash64 = crc64.New(crc64.MakeTable(crc64.ECMA))
h.Write(data)
return int(h.Sum64()) % numShards
}
func main() {
numShards := 10
for i := 0; i < 5; i++ {
key := fmt.Sprintf("user_%d", i)
shardNum := shard([]byte(key), numShards)
fmt.Printf("%s -> 分片 %d\n", key, shardNum)
}
}
运行:
$ go run main.go
user_0 -> 分片 3
user_1 -> 分片 7
user_2 -> 分片 1
user_3 -> 分片 9
user_4 -> 分片 4
六、最佳实践
1. 复用哈希器
// 推荐:复用哈希器
h := crc32.NewIEEE()
for _, data := range dataList {
h.Reset()
h.Write(data)
checksum := h.Sum32()
// 使用 checksum
}
// 不推荐:每次都创建新实例
for _, data := range dataList {
h := crc32.NewIEEE()
h.Write(data)
checksum := h.Sum32()
}
2. 流式处理大文件
func hashLargeFile(path string) (uint32, error) {
file, err := os.Open(path)
if err != nil {
return 0, err
}
defer file.Close()
h := crc32.NewIEEE()
buf := make([]byte, 32*1024)
for {
n, err := file.Read(buf)
if n > 0 {
h.Write(buf[:n])
}
if err == io.EOF {
break
}
if err != nil {
return 0, err
}
}
return h.Sum32(), nil
}
3. 组合哈希
func combinedHash(data1, data2 []byte) uint32 {
h := crc32.NewIEEE()
h.Write(data1)
h.Write([]byte{0x00}) // 分隔符
h.Write(data2)
return h.Sum32()
}
七、快速参考
接口对比
| 接口 | 继承 | 特有方法 | 返回值类型 | 示例实现 |
|---|---|---|---|---|
| Hash | - | Sum, Reset, Size, BlockSize | []byte | 所有哈希 |
| Hash32 | Hash | Sum32 | uint32 | CRC32 |
| Hash64 | Hash | Sum64 | uint64 | CRC64 |
核心方法
| 方法 | 说明 | 返回值 | 示例 |
|---|---|---|---|
| Write(p []byte) | 写入数据 | (int, error) | h.Write([]byte("data")) |
| Sum(in []byte) | 计算哈希 | []byte | h.Sum(nil) |
| Reset() | 重置哈希器 | - | h.Reset() |
| Size() | 哈希长度 | int | h.Size() (4 或 8) |
| BlockSize() | 块大小 | int | h.BlockSize() |
| Sum32() | 32 位哈希 | uint32 | h.Sum32() |
| Sum64() | 64 位哈希 | uint64 | h.Sum64() |
常见哈希实现
| 包 | 类型 | 函数 | 返回值 |
|---|---|---|---|
| hash/crc32 | Hash32 | NewIEEE() | CRC32 IEEE |
| hash/crc32 | Hash32 | NewMakeTable() | 自定义表 |
| hash/crc64 | Hash64 | New(table) | CRC64 |
| hash/adler32 | Hash32 | New() | Adler-32 |
| hash/maphash | Hash64 | New() | 快速非加密 |
使用模式
| 场景 | 推荐方法 | 说明 |
|---|---|---|
| 单次计算 | Write + Sum | 简单直接 |
| 流式计算 | 多次 Write + Sum | 分块处理 |
| 重复使用 | Reset + Write + Sum | 性能优化 |
| 整数结果 | Sum32/Sum64 | 方便比较 |
| 字节结果 | Sum(nil) | 通用格式 |
八、与其他包配合
与 io 包配合
package main
import (
"fmt"
"hash/crc32"
"io"
"strings"
)
func main() {
h := crc32.NewIEEE()
// 使用 io.WriteString
io.WriteString(h, "Hello")
io.WriteString(h, ", ")
io.WriteString(h, "World!")
fmt.Printf("CRC32: %08x\n", h.Sum32())
// 使用 io.Copy(从 Reader)
h.Reset()
reader := strings.NewReader("data")
io.Copy(h, reader)
fmt.Printf("From Reader: %08x\n", h.Sum32())
}
与 encoding/hex 配合
package main
import (
"encoding/hex"
"fmt"
"hash/crc32"
)
func main() {
h := crc32.NewIEEE()
h.Write([]byte("data"))
sum := h.Sum(nil)
// 十六进制编码
hexStr := hex.EncodeToString(sum)
fmt.Printf("Hex: %s\n", hexStr)
// 解码验证
decoded, _ := hex.DecodeString(hexStr)
fmt.Printf("Decoded: %x\n", decoded)
}
运行:
$ go run main.go
Hex: 696ef3d0
Decoded: 696ef3d0
最后更新:2026-04-04
Go 版本:Go 1.23+