选fnv-1a因其轻量高效,仅需乘法、异或和溢出截断,适合哈希表键、去重等非密码场景;md5/sha体积大、计算慢;实现需用无符号整型、逐字节处理并强制转unsigned char。

为什么选FNV-1a而不是MD5或SHA?
因为你要的是“简单摘要”,不是密码学安全哈希。MD5、SHA系列有大块初始化、轮函数、填充逻辑,编译后体积大、计算慢;FNV-1a只有乘法+异或+模运算,几行代码搞定,适合做哈希表键、快速去重、配置校验等场景。它不抗碰撞,但日常工程里够用。
FNV-1a的C++实现要点(32位/64位)
核心是两个常量:FNV_offset_basis 和 FNV_prime,不同位宽值不同。32位常用 0x811c9dc5 和 0x01000193;64位用 0xcbf29ce484222325 和 0x100000001b3。注意:必须用无符号整型(uint32_t/uint64_t),否则右移或乘法可能溢出符号位导致结果错乱。
- 逐字节处理,不能按
char*直接reinterpret_cast成int批量读——会受字节序和对齐影响 - 每轮先异或当前字节,再乘质数:
hash = (hash ^ byte) * FNV_prime - 不需要显式取模——利用无符号整型溢出自动截断(32位即 mod 2³²)
- 空字符串返回
FNV_offset_basis,不是0
一个安全可用的模板函数示例
下面这段代码可直接复制使用,支持std::string和C风格字符串,且自动推导位宽:
template<typename t="uint32_t">
T fnv1a(const char* s, size_t len) {
static_assert(std::is_same_v<t uint32_t> || std::is_same_v<t uint64_t>);
T hash = std::is_same_v<t uint32_t> ? 0x811c9dc5u : 0xcbf29ce484222325ull;
const T prime = std::is_same_v<t uint32_t> ? 0x01000193u : 0x100000001b3ull;
for (size_t i = 0; i (s[i]);
hash *= prime;
}
return hash;
}
<p>// 用法:
// auto h32 = fnv1a<uint32_t>(str.c_str(), str.size());
// auto h64 = fnv1a<uint64_t>("hello", 5);
</uint64_t></uint32_t></p></t></t></t></t></typename>
常见踩坑:signed char vs unsigned char
如果你传入的字符串含非ASCII字符(比如UTF-8中文),而编译器默认char是有符号的,那么s[i]可能是负数(如-80),直接异或会把高位全置1,破坏哈希一致性。必须强制转成unsigned char再参与运算——上面示例里那行static_cast<unsigned char>(s[i])</unsigned>不能省。
另一个坑是误用std::string::data()而没传size():C++11后data()不保证末尾有\0,但FNV不依赖终止符,所以一定要用str.size()而非strlen(str.data()),后者遇到中间\0就提前结束。
实际用的时候,别为了省几行自己手写循环去处理std::string_view——直接用.data()和.size()最稳。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











