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.Hash32 | fnv.New32() |
| New32a() | 创建 32 位 FNV-1a 哈希器 | hash.Hash32 | fnv.New32a() |
| New64() | 创建 64 位 FNV-1 哈希器 | hash.Hash64 | fnv.New64() |
| New64a() | 创建 64 位 FNV-1a 哈希器 | hash.Hash64 | fnv.New64a() |
| New128() | 创建 128 位 FNV-1 哈希器 | hash.Hash128 | fnv.New128() |
| New128a() | 创建 128 位 FNV-1a 哈希器 | hash.Hash128 | fnv.New128a() |
Hash32 接口方法
| 方法 | 说明 | 返回值 | 示例 |
|---|---|---|---|
| Write(p []byte) | 写入数据 | (int, error) | h.Write([]byte("data")) |
| Sum(in []byte) | 计算哈希 | []byte | h.Sum(nil) |
| Reset() | 重置哈希器 | - | h.Reset() |
| Size() | 哈希长度 | int | h.Size() (4) |
| BlockSize() | 块大小 | int | h.BlockSize() (1) |
| Sum32() | 32 位哈希值 | uint32 | h.Sum32() |
Hash64 接口方法
| 方法 | 说明 | 返回值 | 示例 |
|---|---|---|---|
| Write(p []byte) | 写入数据 | (int, error) | h.Write([]byte("data")) |
| Sum(in []byte) | 计算哈希 | []byte | h.Sum(nil) |
| Reset() | 重置哈希器 | - | h.Reset() |
| Size() | 哈希长度 | int | h.Size() (8) |
| BlockSize() | 块大小 | int | h.BlockSize() (1) |
| Sum64() | 64 位哈希值 | uint64 | h.Sum64() |
Hash128 接口方法
| 方法 | 说明 | 返回值 | 示例 |
|---|---|---|---|
| Write(p []byte) | 写入数据 | (int, error) | h.Write([]byte("data")) |
| Sum(in []byte) | 计算哈希 | []byte | h.Sum(nil) |
| Reset() | 重置哈希器 | - | h.Reset() |
| Size() | 哈希长度 | int | h.Size() (16) |
| BlockSize() | 块大小 | int | h.BlockSize() (1) |
FNV 变体对比
| 变体 | 位数 | 字节长度 | 推荐度 | 应用场景 |
|---|---|---|---|---|
| FNV-32 | 32 | 4 字节 | ★★★ | 哈希表 |
| FNV-32a | 32 | 4 字节 | ★★★★★ | 哈希表(推荐) |
| FNV-64 | 64 | 8 字节 | ★★★ | 中等数据量 |
| FNV-64a | 64 | 8 字节 | ★★★★★ | 大数据量(推荐) |
| FNV-128 | 128 | 16 字节 | ★★ | 特殊需求 |
| FNV-128a | 128 | 16 字节 | ★★★★ | 特殊需求(推荐) |
使用模式
| 场景 | 推荐方法 | 说明 |
|---|---|---|
| 哈希表 | 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_basis | FNV_prime |
|---|---|---|
| 32 位 | 2166136261 | 16777619 |
| 64 位 | 14695981039346656037 | 1099511628211 |
| 128 位 | 14406626329776981559 | 309485009821345068724781371 |
性能对比
| 算法 | 速度 | 分布 | 适用场景 |
|---|---|---|---|
| FNV-1a | 最快 | 好 | 哈希表、布隆过滤器 |
| MurmurHash | 快 | 很好 | 通用哈希 |
| CityHash | 很快 | 优秀 | 字符串哈希 |
| MD5 | 慢 | 优秀 | 加密(已不安全) |
| SHA-256 | 很慢 | 优秀 | 加密、安全 |
FNV 特性
-
优点:
- 计算速度极快
- 实现简单
- 分布良好
- 适合哈希表
- 低碰撞率(对于非恶意数据)
-
缺点:
- 非加密哈希
- 易受碰撞攻击
- 不适合安全性场景
- 短字符串分布稍差
FNV-1 vs FNV-1a
| 特性 | FNV-1 | FNV-1a |
|---|---|---|
| 操作顺序 | 先乘后异或 | 先异或后乘 |
| 分布 | 好 | 更好 |
| 推荐度 | ★★★ | ★★★★★ |
| 使用场景 | 旧系统 | 新系统(推荐) |
应用场景
- ✓ 哈希表键生成
- ✓ 布隆过滤器
- ✓ 数据分片
- ✓ 校验和(非安全)
- ✓ 快速数据指纹
- ✗ 密码存储
- ✗ 数字签名
- ✗ 防篡改检测
- ✗ 安全令牌
最后更新:2026-04-04
Go 版本:Go 1.23+