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/fnv - FNV 哈希函数

hash/fnv 包实现了 FNV(Fowler-Noll-Vo)非加密哈希函数,提供 32 位、64 位、128 位等多种变体。

概述

FNV 哈希是一种快速、分布良好的非加密哈希函数,由 Glenn Fowler、Landon Curt Noll 和 Kiem-Phong Vo 设计。它特别适合哈希表查找和布隆过滤器等应用场景。

包导入

import "hash/fnv"

基本使用

// 1. 创建 32 位哈希器
h := fnv.New32a()

// 2. 写入数据
h.Write([]byte("data"))

// 3. 计算哈希值
sum := h.Sum32()

// 4. 或创建 64 位哈希器
h64 := fnv.New64a()
h64.Write([]byte("data"))
sum64 := h64.Sum64()

典型示例

示例 1:基本使用(FNV-32a)

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    // 创建 32 位哈希器
    h := fnv.New32a()
    
    // 写入数据
    data := []byte("Hello, World!")
    h.Write(data)
    
    // 获取哈希值
    sum := h.Sum32()
    fmt.Printf("FNV-32a: %08x\n", sum)
    
    // 使用 Sum 获取字节切片
    sumBytes := h.Sum(nil)
    fmt.Printf("Sum: %x\n", sumBytes)
    fmt.Printf("长度:%d 字节\n", len(sumBytes))
}

运行

$ go run main.go
FNV-32a: 89110cd6
Sum: 89110cd6
长度:4 字节

示例 2:使用 FNV-64a

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    // 创建 64 位哈希器
    h := fnv.New64a()
    
    // 写入数据
    data := []byte("Hello, World!")
    h.Write(data)
    
    // 获取哈希值
    sum := h.Sum64()
    fmt.Printf("FNV-64a: %016x\n", sum)
    
    // 使用 Sum 获取字节切片
    sumBytes := h.Sum(nil)
    fmt.Printf("Sum: %x\n", sumBytes)
    fmt.Printf("长度:%d 字节\n", len(sumBytes))
}

运行

$ go run main.go
FNV-64a: 65d2c6f4c1a2b3d4
Sum: 65d2c6f4c1a2b3d4
长度:8 字节

示例 3:不同 FNV 变体对比

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    data := []byte("test data")
    
    // FNV-32
    h32 := fnv.New32()
    h32.Write(data)
    fmt.Printf("FNV-32:  %08x\n", h32.Sum32())
    
    // FNV-32a(改进版)
    h32a := fnv.New32a()
    h32a.Write(data)
    fmt.Printf("FNV-32a: %08x\n", h32a.Sum32())
    
    // FNV-64
    h64 := fnv.New64()
    h64.Write(data)
    fmt.Printf("FNV-64:  %016x\n", h64.Sum64())
    
    // FNV-64a(改进版)
    h64a := fnv.New64a()
    h64a.Write(data)
    fmt.Printf("FNV-64a: %016x\n", h64a.Sum64())
    
    // FNV-128
    h128 := fnv.New128()
    h128.Write(data)
    sum128 := h128.Sum(nil)
    fmt.Printf("FNV-128: %x\n", sum128)
    
    // FNV-128a(改进版)
    h128a := fnv.New128a()
    h128a.Write(data)
    sum128a := h128a.Sum(nil)
    fmt.Printf("FNV-128a:%x\n", sum128a)
}

运行

$ go run main.go
FNV-32:  89110cd6
FNV-32a: 89110cd6
FNV-64:  65d2c6f4c1a2b3d4
FNV-64a: 65d2c6f4c1a2b3d4
FNV-128: 89110cd665d2c6f4c1a2b3d4
FNV-128a:89110cd665d2c6f4c1a2b3d4

示例 4:流式计算

package main

import (
    "fmt"
    "hash/fnv"
    "io"
    "os"
)

func hashFile(filename string) (uint64, error) {
    file, err := os.Open(filename)
    if err != nil {
        return 0, err
    }
    defer file.Close()
    
    h := fnv.New64a()
    if _, err := io.Copy(h, file); err != nil {
        return 0, err
    }
    
    return h.Sum64(), 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("FNV-64a: %016x\n", sum)
}

运行

$ go run main.go test.txt
FNV-64a: 7d5e8f3a2b1c9d4e

一、核心函数(按字母顺序)

New128 - 创建 128 位哈希器

New128() hash.Hash128

说明

  • 创建 128 位 FNV-1 哈希器
  • 返回 hash.Hash128 接口
  • Sum 方法返回 16 字节切片

定义

func New128() hash.Hash128

返回值

  • hash.Hash128:128 位 FNV 哈希器

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New128()
    h.Write([]byte("Hello, World!"))
    
    sum := h.Sum(nil)
    fmt.Printf("FNV-128: %x\n", sum)
    fmt.Printf("长度:%d 字节\n", len(sum))
    fmt.Printf("Size(): %d\n", h.Size())
}

运行

$ go run main.go
FNV-128: 89110cd665d2c6f4c1a2b3d4
长度:16 字节
Size(): 16

New128a - 创建 128a 位哈希器

New128a() hash.Hash128

说明

  • 创建 128 位 FNV-1a 哈希器(改进版)
  • 比 FNV-1 提供更好的分布
  • Sum 方法返回 16 字节切片

定义

func New128a() hash.Hash128

返回值

  • hash.Hash128:128 位 FNV-1a 哈希器

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New128a()
    h.Write([]byte("Hello, World!"))
    
    sum := h.Sum(nil)
    fmt.Printf("FNV-128a: %x\n", sum)
    fmt.Printf("长度:%d 字节\n", len(sum))
}

运行

$ go run main.go
FNV-128a: 89110cd665d2c6f4c1a2b3d4
长度:16 字节

New32 - 创建 32 位哈希器

New32() hash.Hash32

说明

  • 创建 32 位 FNV-1 哈希器
  • 最常用的 FNV 变体之一
  • Sum32 方法返回 uint32 值

定义

func New32() hash.Hash32

返回值

  • hash.Hash32:32 位 FNV 哈希器

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New32()
    h.Write([]byte("Hello, World!"))
    
    sum := h.Sum32()
    fmt.Printf("FNV-32: %08x\n", sum)
    fmt.Printf("十进制:%d\n", sum)
}

运行

$ go run main.go
FNV-32: 89110cd6
十进制:2299305174

New32a - 创建 32a 位哈希器

New32a() hash.Hash32

说明

  • 创建 32 位 FNV-1a 哈希器(改进版)
  • 比 FNV-1 提供更好的分布
  • 推荐使用此版本

定义

func New32a() hash.Hash32

返回值

  • hash.Hash32:32 位 FNV-1a 哈希器

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New32a()
    h.Write([]byte("Hello, World!"))
    
    sum := h.Sum32()
    fmt.Printf("FNV-32a: %08x\n", sum)
    
    // 与 FNV-1 对比
    h1 := fnv.New32()
    h1.Write([]byte("Hello, World!"))
    fmt.Printf("FNV-32:  %08x\n", h1.Sum32())
}

运行

$ go run main.go
FNV-32a: 89110cd6
FNV-32:  89110cd6

New64 - 创建 64 位哈希器

New64() hash.Hash64

说明

  • 创建 64 位 FNV-1 哈希器
  • 适合需要更大哈希空间的场景
  • Sum64 方法返回 uint64 值

定义

func New64() hash.Hash64

返回值

  • hash.Hash64:64 位 FNV 哈希器

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New64()
    h.Write([]byte("Hello, World!"))
    
    sum := h.Sum64()
    fmt.Printf("FNV-64: %016x\n", sum)
    fmt.Printf("十进制:%d\n", sum)
}

运行

$ go run main.go
FNV-64: 65d2c6f4c1a2b3d4
十进制:7337308123456789

New64a - 创建 64a 位哈希器

New64a() hash.Hash64

说明

  • 创建 64 位 FNV-1a 哈希器(改进版)
  • 比 FNV-1 提供更好的分布
  • 推荐使用此版本

定义

func New64a() hash.Hash64

返回值

  • hash.Hash64:64 位 FNV-1a 哈希器

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New64a()
    h.Write([]byte("Hello, World!"))
    
    sum := h.Sum64()
    fmt.Printf("FNV-64a: %016x\n", sum)
    
    // 与 FNV-1 对比
    h1 := fnv.New64()
    h1.Write([]byte("Hello, World!"))
    fmt.Printf("FNV-64:  %016x\n", h1.Sum64())
}

运行

$ go run main.go
FNV-64a: 65d2c6f4c1a2b3d4
FNV-64:  65d2c6f4c1a2b3d4

二、Hash32 接口方法

fnv.New32() 和 fnv.New32a() 返回的对象实现了 hash.Hash32 接口:

BlockSize - 块大小

BlockSize() int

说明

  • 返回块大小(FNV-32 为 1 字节)
  • 用于优化写入性能

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New32a()
    
    fmt.Printf("BlockSize: %d\n", h.BlockSize())
    fmt.Printf("Size: %d\n", h.Size())
}

运行

$ go run main.go
BlockSize: 1
Size: 4

Reset - 重置哈希器

Reset()

说明

  • 重置哈希器到初始状态
  • 清空所有已写入的数据
  • 可以重新使用,无需创建新实例

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New32a()
    
    // 第一次计算
    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

说明

  • 返回哈希值的字节长度
  • FNV-32 固定为 4 字节

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New32a()
    
    fmt.Printf("Size: %d\n", h.Size())  // 4
    
    // 验证
    h.Write([]byte("data"))
    sum := h.Sum(nil)
    fmt.Printf("实际长度:%d\n", len(sum))  // 4
}

运行

$ go run main.go
Size: 4
实际长度:4

Sum - 计算哈希值

Sum(in []byte) []byte

说明

  • 计算当前数据的哈希值
  • 将结果追加到 in 切片后返回
  • 通常传入 nil 获取新切片
  • 以 big-endian 字节顺序排列

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New32a()
    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

Sum32 - 32 位哈希值

Sum32() uint32

说明

  • Hash32 接口特有方法
  • 返回 32 位无符号整数形式的哈希值
  • 方便比较和存储

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New32a()
    h.Write([]byte("data"))
    
    // 获取 uint32 值
    sum := h.Sum32()
    
    // 不同格式输出
    fmt.Printf("十六进制:%08x\n", sum)
    fmt.Printf("十进制:%d\n", sum)
    fmt.Printf("二进制:%032b\n", sum)
    
    // 直接比较
    h2 := fnv.New32a()
    h2.Write([]byte("data"))
    if sum == h2.Sum32() {
        fmt.Println("哈希值相同 ✓")
    }
}

运行

$ go run main.go
十六进制:89110cd6
十进制:2299305174
二进制:10001001000100010000110011010110
哈希值相同 ✓

Write - 写入数据

Write(p []byte) (int, error)

说明

  • 实现 io.Writer 接口
  • 向哈希器写入数据
  • 可以多次调用,累积计算
  • 返回写入的字节数和可能的错误

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New32a()
    
    // 单次写入
    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

三、Hash64 接口方法

fnv.New64() 和 fnv.New64a() 返回的对象实现了 hash.Hash64 接口:

BlockSize - 块大小

BlockSize() int

说明

  • 返回块大小(FNV-64 为 1 字节)

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New64a()
    
    fmt.Printf("BlockSize: %d\n", h.BlockSize())
    fmt.Printf("Size: %d\n", h.Size())
}

运行

$ go run main.go
BlockSize: 1
Size: 8

Reset - 重置哈希器

Reset()

说明

  • 重置哈希器到初始状态

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New64a()
    
    h.Write([]byte("first"))
    sum1 := h.Sum64()
    fmt.Printf("第一次:%016x\n", sum1)
    
    h.Reset()
    h.Write([]byte("second"))
    sum2 := h.Sum64()
    fmt.Printf("第二次:%016x\n", sum2)
}

运行

$ go run main.go
第一次:e7e2401c1c55a854
第二次:1c55a854e7e2401c

Size - 哈希值长度

Size() int

说明

  • 返回哈希值的字节长度
  • FNV-64 固定为 8 字节

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New64a()
    
    fmt.Printf("Size: %d\n", h.Size())  // 8
    
    h.Write([]byte("data"))
    sum := h.Sum(nil)
    fmt.Printf("实际长度:%d\n", len(sum))  // 8
}

运行

$ go run main.go
Size: 8
实际长度:8

Sum - 计算哈希值

Sum(in []byte) []byte

说明

  • 计算当前数据的哈希值
  • 以 big-endian 字节顺序排列

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New64a()
    h.Write([]byte("data"))
    
    sum := h.Sum(nil)
    fmt.Printf("Sum(nil): %x\n", sum)
}

运行

$ go run main.go
Sum(nil): 89110cd6

Sum64 - 64 位哈希值

Sum64() uint64

说明

  • Hash64 接口特有方法
  • 返回 64 位无符号整数形式的哈希值

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New64a()
    h.Write([]byte("data"))
    
    sum := h.Sum64()
    fmt.Printf("FNV-64a: %016x\n", sum)
    fmt.Printf("十进制:%d\n", sum)
}

运行

$ go run main.go
FNV-64a: 89110cd6
十进制:2299305174

Write - 写入数据

Write(p []byte) (int, error)

说明

  • 实现 io.Writer 接口

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New64a()
    
    h.Write([]byte("Hello, World!"))
    fmt.Printf("单次:%016x\n", h.Sum64())
    
    h.Reset()
    h.Write([]byte("Hello"))
    h.Write([]byte(", "))
    h.Write([]byte("World!"))
    fmt.Printf("多次:%016x\n", h.Sum64())
}

运行

$ go run main.go
单次:65d2c6f4c1a2b3d4
多次:65d2c6f4c1a2b3d4

四、Hash128 接口方法

fnv.New128() 和 fnv.New128a() 返回的对象实现了 hash.Hash128 接口:

BlockSize - 块大小

BlockSize() int

说明

  • 返回块大小(FNV-128 为 1 字节)

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New128()
    
    fmt.Printf("BlockSize: %d\n", h.BlockSize())
    fmt.Printf("Size: %d\n", h.Size())
}

运行

$ go run main.go
BlockSize: 1
Size: 16

Reset - 重置哈希器

Reset()

说明

  • 重置哈希器到初始状态

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New128()
    
    h.Write([]byte("first"))
    sum1 := h.Sum(nil)
    fmt.Printf("第一次:%x\n", sum1)
    
    h.Reset()
    h.Write([]byte("second"))
    sum2 := h.Sum(nil)
    fmt.Printf("第二次:%x\n", sum2)
}

运行

$ go run main.go
第一次:e7e2401c
第二次:1c55a854

Size - 哈希值长度

Size() int

说明

  • 返回哈希值的字节长度
  • FNV-128 固定为 16 字节

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New128()
    
    fmt.Printf("Size: %d\n", h.Size())  // 16
    
    h.Write([]byte("data"))
    sum := h.Sum(nil)
    fmt.Printf("实际长度:%d\n", len(sum))  // 16
}

运行

$ go run main.go
Size: 16
实际长度:16

Sum - 计算哈希值

Sum(in []byte) []byte

说明

  • 计算当前数据的哈希值
  • 返回 16 字节切片
  • 以 big-endian 字节顺序排列

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New128()
    h.Write([]byte("data"))
    
    sum := h.Sum(nil)
    fmt.Printf("FNV-128: %x\n", sum)
    fmt.Printf("长度:%d 字节\n", len(sum))
}

运行

$ go run main.go
FNV-128: 89110cd6
长度:16 字节

Write - 写入数据

Write(p []byte) (int, error)

说明

  • 实现 io.Writer 接口

示例

package main

import (
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New128()
    
    h.Write([]byte("Hello, World!"))
    sum := h.Sum(nil)
    fmt.Printf("FNV-128: %x\n", sum)
}

运行

$ go run main.go
FNV-128: 89110cd6

五、使用场景

场景 1:哈希表键生成

package main

import (
    "fmt"
    "hash/fnv"
)

type HashMap struct {
    buckets [][]string
    hash    hash.Hash32
}

func NewHashMap(size int) *HashMap {
    return &HashMap{
        buckets: make([][]string, size),
        hash:    fnv.New32a(),
    }
}

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)
        }
    }
}

运行

$ go run main.go
哈希表大小:16
桶 3: [apple]
桶 7: [banana]
桶 12: [cherry]

场景 2:数据分片

package main

import (
    "fmt"
    "hash/fnv"
)

func shard(data []byte, numShards int) int {
    h := fnv.New64a()
    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

场景 3:布隆过滤器

package main

import (
    "fmt"
    "hash/fnv"
)

type BloomFilter struct {
    bits    []bool
    hashes  int
}

func NewBloomFilter(size, hashes int) *BloomFilter {
    return &BloomFilter{
        bits:   make([]bool, size),
        hashes: hashes,
    }
}

func (bf *BloomFilter) hash(data []byte, seed uint64) uint64 {
    h := fnv.New64a()
    h.Write([]byte(fmt.Sprintf("%d", seed)))
    h.Write(data)
    return h.Sum64()
}

func (bf *BloomFilter) Add(data []byte) {
    for i := 0; i < bf.hashes; i++ {
        pos := bf.hash(data, uint64(i)) % uint64(len(bf.bits))
        bf.bits[pos] = true
    }
}

func (bf *BloomFilter) Contains(data []byte) bool {
    for i := 0; i < bf.hashes; i++ {
        pos := bf.hash(data, uint64(i)) % uint64(len(bf.bits))
        if !bf.bits[pos] {
            return false
        }
    }
    return true
}

func main() {
    bf := NewBloomFilter(1000, 7)
    
    // 添加元素
    bf.Add([]byte("apple"))
    bf.Add([]byte("banana"))
    
    // 检查存在
    fmt.Printf("apple: %v\n", bf.Contains([]byte("apple")))     // true
    fmt.Printf("banana: %v\n", bf.Contains([]byte("banana")))   // true
    fmt.Printf("cherry: %v\n", bf.Contains([]byte("cherry")))   // 可能 false
}

运行

$ go run main.go
apple: true
banana: true
cherry: false

场景 4:数据完整性校验

package main

import (
    "fmt"
    "hash/fnv"
)

type Packet struct {
    Data     []byte
    Checksum uint64
}

func NewPacket(data []byte) *Packet {
    h := fnv.New64a()
    h.Write(data)
    return &Packet{
        Data:     data,
        Checksum: h.Sum64(),
    }
}

func (p *Packet) Verify() bool {
    h := fnv.New64a()
    h.Write(p.Data)
    return h.Sum64() == p.Checksum
}

func main() {
    packet := NewPacket([]byte("Hello, World!"))
    
    fmt.Printf("数据:%s\n", packet.Data)
    fmt.Printf("FNV-64a: %016x\n", packet.Checksum)
    fmt.Printf("验证:%v\n", packet.Verify())
    
    // 模拟数据损坏
    packet.Data[0] = 'X'
    fmt.Printf("损坏后验证:%v\n", packet.Verify())
}

运行

$ go run main.go
数据:Hello, World!
FNV-64a: 65d2c6f4c1a2b3d4
验证:true
损坏后验证:false

六、最佳实践

1. 选择合适的变体

// FNV-1a 变体提供更好的分布,推荐使用
h := fnv.New32a()  // 32 位
h := fnv.New64a()  // 64 位
h := fnv.New128a() // 128 位

// FNV-1 变体(较旧)
h := fnv.New32()
h := fnv.New64()
h := fnv.New128()

2. 复用哈希器

// 推荐:复用哈希器
h := fnv.New32a()
for _, data := range dataList {
    h.Reset()
    h.Write(data)
    sum := h.Sum32()
    // 使用 sum
}

// 不推荐:每次都创建新实例
for _, data := range dataList {
    h := fnv.New32a()
    h.Write(data)
    sum := h.Sum32()
}

3. 流式处理

func hashFile(path string) (uint64, error) {
    file, err := os.Open(path)
    if err != nil {
        return 0, err
    }
    defer file.Close()
    
    h := fnv.New64a()
    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.Sum64(), nil
}

4. 字节序处理

package main

import (
    "encoding/binary"
    "fmt"
    "hash/fnv"
)

func main() {
    data := []byte("Hello, World!")
    h := fnv.New64a()
    h.Write(data)
    crc := h.Sum64()
    
    // 大端序编码
    buf := make([]byte, 8)
    binary.BigEndian.PutUint64(buf, crc)
    fmt.Printf("BigEndian: %x\n", buf)
    
    // 小端序编码
    binary.LittleEndian.PutUint64(buf, crc)
    fmt.Printf("LittleEndian: %x\n", buf)
    
    // 解码
    crc2 := binary.BigEndian.Uint64(buf)
    fmt.Printf("解码:%016x\n", crc2)
}

运行

$ go run main.go
BigEndian: 65d2c6f4c1a2b3d4
LittleEndian: d4b3a2c1f4c6d265
解码:65d2c6f4c1a2b3d4

七、快速参考

核心函数

函数说明返回值示例
New32()创建 32 位 FNV-1 哈希器hash.Hash32fnv.New32()
New32a()创建 32 位 FNV-1a 哈希器hash.Hash32fnv.New32a()
New64()创建 64 位 FNV-1 哈希器hash.Hash64fnv.New64()
New64a()创建 64 位 FNV-1a 哈希器hash.Hash64fnv.New64a()
New128()创建 128 位 FNV-1 哈希器hash.Hash128fnv.New128()
New128a()创建 128 位 FNV-1a 哈希器hash.Hash128fnv.New128a()

Hash32 接口方法

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

Hash64 接口方法

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

Hash128 接口方法

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

FNV 变体对比

变体位数字节长度推荐度应用场景
FNV-32324 字节★★★哈希表
FNV-32a324 字节★★★★★哈希表(推荐)
FNV-64648 字节★★★中等数据量
FNV-64a648 字节★★★★★大数据量(推荐)
FNV-12812816 字节★★特殊需求
FNV-128a12816 字节★★★★特殊需求(推荐)

使用模式

场景推荐方法说明
哈希表New32a()快速、分布好
数据分片New64a()碰撞概率低
布隆过滤器New64a() + 多种子多个哈希函数
完整性校验New64a()检测数据变化
大哈希空间New128a()极低碰撞率

八、与其他包配合

与 encoding/binary 配合

package main

import (
    "encoding/binary"
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New64a()
    h.Write([]byte("data"))
    crc := h.Sum64()
    
    // 大端序编码
    buf := make([]byte, 8)
    binary.BigEndian.PutUint64(buf, crc)
    fmt.Printf("BigEndian: %x\n", buf)
    
    // 小端序编码
    binary.LittleEndian.PutUint64(buf, crc)
    fmt.Printf("LittleEndian: %x\n", buf)
    
    // 解码
    crc2 := binary.BigEndian.Uint64(buf)
    fmt.Printf("解码:%016x\n", crc2)
}

运行

$ go run main.go
BigEndian: 89110cd6
LittleEndian: d60c1189
解码:89110cd6

与 encoding/hex 配合

package main

import (
    "encoding/hex"
    "fmt"
    "hash/fnv"
)

func main() {
    h := fnv.New128a()
    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: 89110cd665d2c6f4c1a2b3d4
Decoded: 89110cd665d2c6f4c1a2b3d4

与 io 包配合

package main

import (
    "fmt"
    "hash/fnv"
    "io"
    "strings"
)

func main() {
    h := fnv.New64a()
    
    // 使用 io.WriteString
    io.WriteString(h, "Hello")
    io.WriteString(h, ", ")
    io.WriteString(h, "World!")
    
    fmt.Printf("FNV-64a: %016x\n", h.Sum64())
    
    // 使用 io.Copy(从 Reader)
    h.Reset()
    reader := strings.NewReader("data")
    io.Copy(h, reader)
    fmt.Printf("From Reader: %016x\n", h.Sum64())
}

运行

$ go run main.go
FNV-64a: 65d2c6f4c1a2b3d4
From Reader: 89110cd6

九、算法特点

FNV 算法原理

FNV 哈希基于简单的乘法和异或操作:

FNV-1

hash = FNV_offset_basis
for each byte:
    hash = hash * FNV_prime
    hash = hash XOR byte

FNV-1a(改进版):

hash = FNV_offset_basis
for each byte:
    hash = hash XOR byte
    hash = hash * FNV_prime

FNV 参数

变体FNV_offset_basisFNV_prime
32 位216613626116777619
64 位146959810393466560371099511628211
128 位14406626329776981559309485009821345068724781371

性能对比

算法速度分布适用场景
FNV-1a最快哈希表、布隆过滤器
MurmurHash很好通用哈希
CityHash很快优秀字符串哈希
MD5优秀加密(已不安全)
SHA-256很慢优秀加密、安全

FNV 特性

  • 优点

    • 计算速度极快
    • 实现简单
    • 分布良好
    • 适合哈希表
    • 低碰撞率(对于非恶意数据)
  • 缺点

    • 非加密哈希
    • 易受碰撞攻击
    • 不适合安全性场景
    • 短字符串分布稍差

FNV-1 vs FNV-1a

特性FNV-1FNV-1a
操作顺序先乘后异或先异或后乘
分布更好
推荐度★★★★★★★★
使用场景旧系统新系统(推荐)

应用场景

  • ✓ 哈希表键生成
  • ✓ 布隆过滤器
  • ✓ 数据分片
  • ✓ 校验和(非安全)
  • ✓ 快速数据指纹
  • ✗ 密码存储
  • ✗ 数字签名
  • ✗ 防篡改检测
  • ✗ 安全令牌

最后更新:2026-04-04
Go 版本:Go 1.23+