导读: 本文从哈希表传统设计与解决思路入手,深入浅出地引出新的设计思路:从尽量规避哈希冲突,转向了利⽤合适的哈希冲突概率来优化计算和存储效率。新的哈希表设计表明 SIMD 指令的并⾏化处理能⼒的有效应⽤能⼤幅度提升哈希表对哈希冲突的容忍能⼒,进⽽提升查询的速度,并且能帮助哈希表进⾏极致的存储空间压缩。
1 背景
哈希表是⼀种查找性能⾮常优异的数据结构,它在计算机系统中存在着⼴泛的应⽤。尽管哈希表理论上 的查找时间复杂度是 O(1),但不同的哈希表在实现上仍然存在巨⼤的性能差异,因⽽⼯程师们对更优秀 哈希数据结构的探索也从未停⽌。
1.1 哈希表设计的核⼼
从计算机理论上来说,哈希表就是⼀个可以通过哈希函数将 Key 映射到 Value 存储位置的数据结构。那么哈希表设计的核⼼就是两点:
1. 怎样提升将Key映射到Value存储位置的效率?
2. 怎样降低存储数据结构的空间开销?
由于存储空间开销也是设计时的⼀个核⼼控制点,在受限于有限的空间情况下,哈希函数的映射算法就存在着⾮常⾼的概率将不同的 Key 映射到同⼀个存储位置,也就是 哈希冲突 。⼤部分哈希表设计的区别,就在于它如何处理哈希冲突。
当遇到哈希冲突时,有⼏种常⻅的解决⽅案:开放寻址法、拉链法、⼆次哈希法。但是下⾯我们介绍两种有趣的、不常⻅的解决思路,并且引出⼀个我们新的实现⽅案—— B16 哈希表。
2 规避哈希冲突
传统哈希表对哈希冲突的处理会增加额外的分⽀跳转和内存访问,这会让流⽔线式的CPU指令处理效率变差。那么肯定就有⼈考虑,怎么能完全规避哈希冲突?所以就有了这样⼀种函数,那就是完美哈希函数(perfect hash function)。
完美哈希函数可以将⼀个 Key 集合⽆冲突地映射到⼀个整数集合中。如果这个⽬标整数集合的⼤⼩与输⼊集合相同,那么它可以被称为最⼩完美哈希函数。
完美哈希函数的设计往往⾮常精巧。例如CMPH(
http://cmph.sourceforge.net/ )函数库提供的 CDZ 完美哈希函数,利⽤了数学上的 ⽆环随机 3-部超图 概念。CDZ通过 3 个不同的 Hash 函数将每个 Key 随机映射到3-部超图的⼀个超边,如果该超图通过⽆环检测,再将每个 Key 映射到超图的⼀个顶点上,然后通过⼀个精⼼设计的与超图顶点数相同的辅助数组取得 Key 最终对应的存储下标。
完美哈希函数听起来很优雅,但事实上也有着实⽤性上的⼀些缺陷:
- 完美哈希函数往往仅能作⽤在限定集合上,即所有可能的 Key 都属于⼀个超集,它⽆法处理没⻅过的 Key;
- 完美哈希函数的构造有⼀定的复杂度,⽽且存在失败的概率;
- 完美哈希函数与密码学上的哈希函数不同,它往往不是⼀个简单的 数学函数 ,⽽是数据 结构+算法 组成的⼀个 功能函数 ,它也有存储空间开销、访问开销和额外的分⽀跳转开销;
但是在指定的场景下,例如只读的场景、集合确定的场景(例如:汉字集合),完美哈希函数可能会取得⾮常好的表现。
3 利⽤哈希冲突
即便不使⽤完美哈希函数,很多哈希表也会刻意控制哈希冲突的概率。最简单的办法是通过控制 Load Fac