1. 哈希函数原理 1.1. 基本概念 哈希函数是一种将任意长度的输入(键)映射到固定长度输出(哈希值)的函数:
1.2. 哈希函数特性
确定性 : 相同的输入总是产生相同的输出,这是哈希函数的基本要求
高效性 : 计算速度快,时间复杂度通常为O(1)或O(n)。
均匀分布 : 好的哈希函数应该使输出在值域内均匀分布,减少哈希冲突的概率。
1.3. 哈希冲突 当两个不同的输入产生相同的哈希值时,称为哈希冲突。
解决冲突的方法:
开放定址法 :线性探测、二次探测
链地址法 :每个桶使用链表存储冲突元素
再哈希法 :使用多个哈希函数
2. 常用哈希算法 2.1. 非加密哈希函数 2.1.1. 多项式滚动哈希 这个算法其实没有特定的名字,只是其哈希算法的原理是多项式滚动。
虽然没有特定的名称,但它是JDK源码中String类的哈希计算方法,在Java中被广泛使用。这个算法的实现非常简单,所以在很多开源的项目中也被采用。
C/C++实现 1 2 3 4 5 6 7 8 9 uint32_t polynomial_rolling (const std::string& str) { int h = 0 ; for (int i = 0 ; i < str.length (); i++) { h = 31 * h + str[i]; } return h; }
JDK源码实现 在Java 8及以后版本中,String.hashCode()的实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public final class String { private int hash; public int hashCode () { int h = hash; if (h == 0 && value.length > 0 ) { char val[] = value; for (int i = 0 ; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; } }
算法讲解 JDK中String类的哈希计算采用的是多项式滚动哈希:
1 h(s) = s[0] × 31^(n-1) + s[1] × 31^(n-2) + ... + s[n-1] × 31^0
数学推导过程如下:
1 2 3 4 5 6 初始: h = 0 第1次: h = 31 × 0 + s[0] = s[0] 第2次: h = 31 × (31 × 0 + s[0]) + s[1] = 31 × s[0] + s[1] 第3次: h = 31 × (31 × s[0] + s[1]) + s[2] = 31² × s[0] + 31 × s[1] + s[2] ... 最终: h = 31^(n-1) × s[0] + 31^(n-2) × s[1] + ... + 31^0 × s[n-1]
为什么选择31?
质数性质: 31是质数,减少哈希冲突。质数乘法有更好的分布特性。
性能优化: 31 * i 可以被JVM优化为 (i << 5) - i。移位操作比乘法快很多。
历史原因: 经过大量测试,31在各种场景下表现良好。在ASCII字符范围内分布均匀。
2.1.2. DJB2 算法实现 1 2 3 4 5 6 7 8 9 10 uint32_t DJB2 (const std::string& str) { uint32_t DJB2 = 5381 ; for (char c : str) { DJB2 = ((DJB2 << 5 ) + DJB2) + c; } return DJB2; }
算法讲解
初始值: 5381(经过大量测试选择的质数)
核心操作: hash = hash * 33 + char
乘法优化: hash * 33 可以优化为 (hash << 5) + hash
特点: 简单高效,分布均匀
2.1.3. FNV-1/FNV-1a Fowler-Noll-Vo哈希算法,有两种变体。
算法实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 static const uint32_t FNV_OFFSET_BASIS = 2166136261u ;static const uint32_t FNV_PRIME = 16777619u ;static uint32_t fnv1_32 (const std::string& str) { uint32_t hash = FNV_OFFSET_BASIS; for (char c : str) { hash = (hash * FNV_PRIME) ^ c; } return hash; } static uint32_t fnv1a_32 (const std::string& str) { uint32_t hash = FNV_OFFSET_BASIS; for (char c : str) { hash = (hash ^ c) * FNV_PRIME; } return hash; } static const uint64_t FNV_OFFSET_BASIS_64 = 14695981039346656037u ;static const uint64_t FNV_PRIME_64 = 1099511628211u ;static uint64_t fnv1a_64 (const std::string& str) { uint64_t hash = FNV_OFFSET_BASIS_64; for (char c : str) { hash = (hash ^ static_cast <uint8_t >(c)) * FNV_PRIME_64; } return hash; }
算法讲解
FNV-1: 先乘后异或 hash = (hash * prime) ^ byte
。
FNV-1a: 先异或后乘 hash = (hash ^ byte) * prime
。
FNV-1a更好: 对短字符串和零字节处理更好。
质数选择: 精心选择的质数保证良好分布。
2.1.4. Jenkins Bob Jenkins开发了一系列高质量的哈希函数,以出色的分布性和低冲突率著称。
主要算法成员:
one-at-a-time
: 简单但高质量的通用哈希
lookup2
: 经典的32位哈希
lookup3
: 改进的64位友好版本
SpookyHash
: 更现代的快速哈希
CityHash
: Google基于Jenkins工作的扩展
Bob Jenkins是哈希算法领域的传奇人物,其设计的Lookup3等哈希函数以卓越性能和低碰撞率成为行业基石,深刻影响了后世算法发展,并被数据库、编译器等多种核心软件广泛采用。
算法实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 uint32_t one_at_a_time (const std::string& str) { uint32_t hash = 0 ; for (char c : str) { hash += static_cast <uint8_t >(c); hash += (hash << 10 ); hash ^= (hash >> 6 ); } hash += (hash << 3 ); hash ^= (hash >> 11 ); hash += (hash << 15 ); return hash; } uint32_t lookup3 (const void * key, size_t length, uint32_t initval = 0 ) { uint32_t a, b, c; const uint8_t * k = static_cast <const uint8_t *>(key); a = b = c = 0xdeadbeef + static_cast <uint32_t >(length) + initval; while (length > 12 ) { a += k[0 ] + (static_cast <uint32_t >(k[1 ]) << 8 ) + (static_cast <uint32_t >(k[2 ]) << 16 ) + (static_cast <uint32_t >(k[3 ]) << 24 ); b += k[4 ] + (static_cast <uint32_t >(k[5 ]) << 8 ) + (static_cast <uint32_t >(k[6 ]) << 16 ) + (static_cast <uint32_t >(k[7 ]) << 24 ); c += k[8 ] + (static_cast <uint32_t >(k[9 ]) << 8 ) + (static_cast <uint32_t >(k[10 ]) << 16 ) + (static_cast <uint32_t >(k[11 ]) << 24 ); a -= c; a ^= ((c << 4 ) | (c >> 28 )); c += b; b -= a; b ^= ((a << 6 ) | (a >> 26 )); a += c; c -= b; c ^= ((b << 8 ) | (b >> 24 )); b += a; a -= c; a ^= ((c << 16 ) | (c >> 16 )); c += b; b -= a; b ^= ((a << 19 ) | (a >> 13 )); a += c; c -= b; c ^= ((b << 4 ) | (b >> 28 )); b += a; k += 12 ; length -= 12 ; } switch (length) { case 12 : c += (static_cast <uint32_t >(k[11 ]) << 24 ); [[fallthrough]]; case 11 : c += (static_cast <uint32_t >(k[10 ]) << 16 ); [[fallthrough]]; case 10 : c += (static_cast <uint32_t >(k[9 ]) << 8 ); [[fallthrough]]; case 9 : c += k[8 ]; [[fallthrough]]; case 8 : b += (static_cast <uint32_t >(k[7 ]) << 24 ); [[fallthrough]]; case 7 : b += (static_cast <uint32_t >(k[6 ]) << 16 ); [[fallthrough]]; case 6 : b += (static_cast <uint32_t >(k[5 ]) << 8 ); [[fallthrough]]; case 5 : b += k[4 ]; [[fallthrough]]; case 4 : a += (static_cast <uint32_t >(k[3 ]) << 24 ); [[fallthrough]]; case 3 : a += (static_cast <uint32_t >(k[2 ]) << 16 ); [[fallthrough]]; case 2 : a += (static_cast <uint32_t >(k[1 ]) << 8 ); [[fallthrough]]; case 1 : a += k[0 ]; break ; case 0 : return c; } c ^= b; c -= ((b << 14 ) | (b >> 18 )); a ^= c; a -= ((c << 11 ) | (c >> 21 )); b ^= a; b -= ((a << 25 ) | (a >> 7 )); c ^= b; c -= ((b << 16 ) | (b >> 16 )); a ^= c; a -= ((c << 4 ) | (c >> 28 )); b ^= a; b -= ((a << 14 ) | (a >> 18 )); c ^= b; c -= ((b << 24 ) | (b >> 8 )); return c; }
算法讲解 one_at_a_time:
这个算法的核心思想是充分混合:
加法
: 引入算术相关性
移位
: 扩散比特位影响
异或
: 引入非线性变换
每个操作都帮助打破输入数据的模式,确保微小的输入变化导致哈希值的巨大变化。
jenkins算法比较:
特性
one-at-a-time
lookup2
lookup3
SpookyHash
CityHash
推出时间
1990s后期
1997
2006
2012
2011
位宽
32位
32位
32/64位
32/64/128位
64/128位
速度
中等
中等
快
非常快
极快
质量
好
优秀
卓越
卓越
优秀
复杂度
简单
中等
中等
中等
复杂
目标平台
通用
32位系统
32/64位
64位优化
64位优化
推荐选择 :
平衡选择 : lookup3
- 质量与性能的最佳平衡
性能优先 : SpookyHash
- 现代系统的优秀选择
简单优先 : one-at-a-time
- 资源受限环境
极致性能 : CityHash
- x86-64服务器环境
2.1.5. MurmurHash 算法实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 uint32_t murmur3_32 (const void * key, size_t len, uint32_t seed = 0 ) { const uint8_t * data = static_cast <const uint8_t *>(key); const int nblocks = len / 4 ; uint32_t h1 = seed; const uint32_t c1 = 0xcc9e2d51 ; const uint32_t c2 = 0x1b873593 ; const uint32_t * blocks = reinterpret_cast <const uint32_t *>(data + nblocks * 4 ); for (int i = -nblocks; i; i++) { uint32_t k1 = blocks[i]; k1 *= c1; k1 = (k1 << 15 ) | (k1 >> 17 ); k1 *= c2; h1 ^= k1; h1 = (h1 << 13 ) | (h1 >> 19 ); h1 = h1 * 5 + 0xe6546b64 ; } const uint8_t * tail = data + nblocks * 4 ; uint32_t k1 = 0 ; switch (len & 3 ) { case 3 : k1 ^= tail[2 ] << 16 ; [[fallthrough]]; case 2 : k1 ^= tail[1 ] << 8 ; [[fallthrough]]; case 1 : k1 ^= tail[0 ]; k1 *= c1; k1 = (k1 << 15 ) | (k1 >> 17 ); k1 *= c2; h1 ^= k1; } h1 ^= len; h1 ^= h1 >> 16 ; h1 *= 0x85ebca6b ; h1 ^= h1 >> 13 ; h1 *= 0xc2b2ae35 ; h1 ^= h1 >> 16 ; return h1; }
算法讲解
名称来源: Multiply and Rotate
- 乘法和旋转操作
开发者: Austin Appleby (2008)
设计目标: 高性能、优良分布、简单实现
应用场景: 哈希表、布隆过滤器、分布式系统
2.2. 加密哈希函数 2.2.1. MD5 MD5 (Message-Digest Algorithm 5)是一种广泛使用的密码散列函数,由Ron Rivest在1991年设计,是MD4算法的改进版。
基本特性:
输出长度
: 128位(16字节)
块大小
: 512位(64字节)
设计目标
: 数据完整性验证、数字签名
现状
: 已不安全 ,不推荐用于安全敏感场景
源码实现:
通常会使用OpenSSL库
来实现MD5哈希函数。OpenSSL 是一个开源的、功能全面的密码学工具库,提供了 SSL/TLS 协议实现以及各种强大的加密、解密和证书管理功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iomanip> #include <openssl/evp.h> #include <openssl/md5.h> #include <sstream> #include <string> std::string md5_hash (const std::string& input) { EVP_MD_CTX* context = EVP_MD_CTX_new (); unsigned char digest[EVP_MAX_MD_SIZE]; unsigned int digest_len; EVP_DigestInit_ex (context, EVP_md5 (), nullptr ); EVP_DigestUpdate (context, input.c_str (), input.length ()); EVP_DigestFinal_ex (context, digest, &digest_len); EVP_MD_CTX_free (context); std::stringstream ss; for (unsigned int i = 0 ; i < digest_len; i++) { ss << std::hex << std::setw (2 ) << std::setfill ('0' ) << static_cast <int >(digest[i]); } return ss.str (); }
2.2.2. SHA SHA系列算法概述 SHA (Secure Hash Algorithm
)是由美国国家安全局设计、美国国家标准与技术研究院发布的一系列密码散列函数。
SHA系列包含多个版本,按发布时间和安全性演进如下:
SHA-0 :1993年发布,因存在安全缺陷,很快被SHA-1取代。
SHA-1 :1995年发布,输出160位(20字节)散列值,曾广泛用于SSL/TLS、Git、数字证书等,但已被证明不安全。
SHA-2 :2001年发布,包含SHA-224、SHA-256、SHA-384、SHA-512等,其中SHA-256
是应用最广泛的版本。
SHA-3 :2015年发布,基于全新的海绵结构设计,作为SHA-2的替代方案,包含SHA3-256、SHA3-512等。
SHA-1 基本特性
输出长度: 160位(20字节)
块大小: 512位
设计年代: 1995年
现状: 已不安全,被证实存在碰撞攻击
SHA-256 基本特性
输出长度: 256位(32字节)
块大小: 512位
所属标准: SHA-2家族
现状: 目前安全,广泛使用
源码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include <iomanip> #include <memory> #include <openssl/evp.h> #include <sstream> #include <string> class SHAHash { public : static std::string sha256 (const std::string& input) { return hash_evp (EVP_sha256 (), input); } static std::string sha1 (const std::string& input) { return hash_evp (EVP_sha1 (), input); } static std::string sha512 (const std::string& input) { return hash_evp (EVP_sha512 (), input); } static std::string sha384 (const std::string& input) { return hash_evp (EVP_sha384 (), input); } static std::string sha224 (const std::string& input) { return hash_evp (EVP_sha224 (), input); } private : static std::string hash_evp (const EVP_MD* md_type, const std::string& input) { std::unique_ptr<EVP_MD_CTX, decltype (&EVP_MD_CTX_free) > context (EVP_MD_CTX_new(), EVP_MD_CTX_free) ; if (!context) { throw std::runtime_error ("Failed to create EVP_MD_CTX" ); } unsigned char digest[EVP_MAX_MD_SIZE]; unsigned int digest_len; if (EVP_DigestInit_ex (context.get (), md_type, nullptr ) != 1 ) { throw std::runtime_error ("EVP_DigestInit_ex failed" ); } if (EVP_DigestUpdate (context.get (), input.c_str (), input.length ()) != 1 ) { throw std::runtime_error ("EVP_DigestUpdate failed" ); } if (EVP_DigestFinal_ex (context.get (), digest, &digest_len) != 1 ) { throw std::runtime_error ("EVP_DigestFinal_ex failed" ); } return bytes_to_hex (digest, digest_len); } static std::string bytes_to_hex (const unsigned char * bytes, size_t length) { std::stringstream ss; for (size_t i = 0 ; i < length; i++) { ss << std::hex << std::setw (2 ) << std::setfill ('0' ) << static_cast <int >(bytes[i]); } return ss.str (); } };
2.3. 专用哈希函数 2.3.1. 整数哈希函数 整数哈希函数的主要目的是改善原始整数的分布特性 ,而不是压缩数据。
应用场景:
源码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 uint32_t thomas_wang (uint32_t key) { key = ~key + (key << 15 ); key = key ^ (key >> 12 ); key = key + (key << 2 ); key = key ^ (key >> 4 ); key = key * 2057 ; key = key ^ (key >> 16 ); return key; } uint32_t multiplicative_hash (uint32_t key, uint32_t table_size) { const double A = 0.6180339887 ; double product = key * A; double fractional = product - static_cast <uint32_t >(product); return static_cast <uint32_t >(table_size * fractional); } uint32_t division_hash (uint32_t key, uint32_t table_size) { return key % table_size; }
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 template <typename K>struct SimpleHasher { size_t operator () (const K& key) const { return key; } }; template <typename K> struct BetterHasher { size_t operator () (const K& key) const { return thomas_wang (key); } }; std::unordered_map<uint32_t , std::string, SimpleHasher<uint32_t >> map1; std::unordered_map<uint32_t , std::string, BetterHasher<uint32_t >> map2;
2.3.2. 布隆过滤器哈希 参见《布隆过滤器 》
3. std::hash 的实现 3.1. 算法概述 在C++里,C++标准没有规定std::hash的具体算法,只定义了接口。各个平台和编译器可以自行实现。
主流编译器的实现:
GCC 4.8+
: 通常使用MurmurHash2变体
Clang:
类似GCC,使用MurmurHash系列
MSVC:
使用FNV-1a或类似算法
3.2. 使用方法 示例代码:
1 2 3 4 5 6 7 8 9 10 void TestStdHash () { std::string str1 ("Hello World!" ) ; std::string str2 ("我爱中国!" ) ; std::cout << "std::hash():" << std::endl; std::hash<std::string> hasher; std::cout << str1 << " --> " << hasher (str1. c_str ()) << std::endl; std::cout << str2 << " --> " << hasher (str2. c_str ()) << std::endl; }
在各个平台下的实现如下:
平台
编译器
Hello World!
我爱中国!
Ubuntu
GCC 13.3.0
10571665718977150164
16981257090658025398
Windows
MSVC 19.44.35214
10092224619179044402
7108835978030019050
macOS
clang 16.0.0
18476351241006313
16995802077979883537
3.3. 注意事项 因为std::hash()的实现具有平台依赖性,在不同的平台实现不一致 。在C++跨平台开发时需要特别注意,特别是需要对hash的结果进行序列化和反序列化存取时,不能直接使用std::hash()的默认接口。
4. 应用场景 哈希函数是计算机科学中的基础工具,具有广泛的应用:
数据结构 :哈希表、集合、缓存
安全领域 :密码存储、数字签名
系统设计 :负载均衡、分布式系统
数据处理 :数据去重、布隆过滤器