Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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所有哈希
Hash32HashSum32uint32CRC32
Hash64HashSum64uint64CRC64

核心方法

方法说明返回值示例
Write(p []byte)写入数据(int, error)h.Write([]byte("data"))
Sum(in []byte)计算哈希[]byteh.Sum(nil)
Reset()重置哈希器-h.Reset()
Size()哈希长度inth.Size() (4 或 8)
BlockSize()块大小inth.BlockSize()
Sum32()32 位哈希uint32h.Sum32()
Sum64()64 位哈希uint64h.Sum64()

常见哈希实现

类型函数返回值
hash/crc32Hash32NewIEEE()CRC32 IEEE
hash/crc32Hash32NewMakeTable()自定义表
hash/crc64Hash64New(table)CRC64
hash/adler32Hash32New()Adler-32
hash/maphashHash64New()快速非加密

使用模式

场景推荐方法说明
单次计算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+