随着存储设备越来越便宜,哈希表以空间换时间的策略也越来越吃香,而其它如二叉树、红黑树、B树,都因为查询速度不够或实现太复杂而在实战中渐渐不被使用。在日益增大的存储需求下,拥有固定slot(桶)数的静态哈希表已经无法适应需要,动态哈希表便应运而生了。
动态哈希表通常是在发生冲突后slot数量翻倍增长,而增长后毕竟哈希函数也变了,所以还要把旧slot里的元素重新放置。这种简单的动态哈希(dynamic hash)算法便是SGI版的STL中hash_map的实现。但如果每次调整slot都要全部重放元素,效率太低,且数据插入的时间也太不均匀:某次插入元素,发生了冲突,于是所有元素重新放置,老半天后这个元素才能插入完毕。作为实时系统,这一突如其来的延时当然是不可忍受的。
为了让数据更新的时间更为均匀,数据库大师 Per-Ake Larson便提出了一种新的动态哈希算法,其实中心思想也很简单(“简单是可靠的前提”——Dijkstra):每次slot数量增长一倍,不是重新放置所有slot中的元素,而只移动一个slot内的元素——就是新增slot的那个兄弟slot(Buddy Slot)。如下:
动态哈希表通常是在发生冲突后slot数量翻倍增长,而增长后毕竟哈希函数也变了,所以还要把旧slot里的元素重新放置。这种简单的动态哈希(dynamic hash)算法便是SGI版的STL中hash_map的实现。但如果每次调整slot都要全部重放元素,效率太低,且数据插入的时间也太不均匀:某次插入元素,发生了冲突,于是所有元素重新放置,老半天后这个元素才能插入完毕。作为实时系统,这一突如其来的延时当然是不可忍受的。
为了让数据更新的时间更为均匀,数据库大师 Per-Ake Larson便提出了一种新的动态哈希算法,其实中心思想也很简单(“简单是可靠的前提”——Dijkstra):每次slot数量增长一倍,不是重新放置所有slot中的元素,而只移动一个slot内的元素——就是新增slot的那个兄弟slot(Buddy Slot)。如下:
![dynamic hashing](https://i-blog.csdnimg.cn/blog_migrate/6c85be41bf8340013491d6564288883a.png)
slot增长前如图a,key为0、6的元素都可以放得下,但当key为12的元素到来时,编号为1的slot里有冲突了,这时需要将slot数翻一倍了,于是变成了图b。此时slot数由原来的4变为了现在的8,但“有效的”slot只有5个:编号分别为0到4。key为12的元素放入4号slot,因为12除8余4。按照一般的Larson策略来讲,4号slot的“兄弟slot”是0号slot,所以在增长完成后0号slot里所有除8余4的元素都要移到“新slot”即4号slot里,举个例子:如果0号slot里有key为16、20、24、28的元素,那key为20、28的元素必须移到4号slot,其它的(16和24)留在0号slot。
只更新一个slot的代价是——查询要稍微复杂些:先用8去除key,如果余数对应的slot“不存在”,则再用4去除,得到正确的slot编号。例如查询6,先 6 mod 8 得6,6号slot还不存在(只是分配了内存,数据结构上还不存在),所以只能再除4,得2,到2号slot去找,找到。图b右下方的公式可能有一些误导:它通过看s(k)是否小于n(<=5)来判断对应的slot是否存在,而在通常的情况下,增长的slot可能不是4号。假如增长的是6号,这时4、5、7号slot是“不存在”的,再用这个公式硬套就不行了。所以关键的判断标准是slot“是否存在”,而不是编号本身是大是小。
Per-Ake Larson的这一经典动态哈希算法以一小点查询时间的损失换来了数据更新的高效和平滑,因此得到了广泛的使用:GNU的gdbm、Berkeley的ndbm、雅虎的mdbm,都是使用这个算法。