首页 > 代码库 > 算法6-4:哈希表现状

算法6-4:哈希表现状

战争故事


很久很久以前,曾经发生过很多关于哈希函数的战争故事。那些战争的基本原理就是通过精心构造造成大量的哈希冲突从而占用大量的CPU资源。



被攻击的软件例有以下例子:

  • 带有漏洞的服务器:攻击者精心构造哈系冲突,只需要56K的网速就能让服务器死机,从而达到DOS攻击的目的。

  • Perl 5.8.0:攻击者精心构造哈系冲突插入到关联数组中

  • Linux 2.4.20 内核:攻击者精心构造文件名,造成大量哈系冲突从而让系统性能骤降。


攻击原理


在Java中的String对象很容易构造哈系冲突。下图展示了Java中哈系冲突的例子。



解决办法


使用更加高级的哈系函数,避免冲突。比如md4 md5 sha0 sha1 sha2 whirlpool ripemd160。但是md4 md5 sha0 sha1目前能够找到缺陷,关于MD5的冲突请戳这里:http://www.links.org/?p=6


MD5不适合用于关联数组,因为开销太大。


两种办法的比较


目前介绍了两种解决冲突的办法,分别是独立链表和线性探针。


独立链表:

  • 删除元素非常方便

  • 随着数据量的增加,性能下降缓慢

  • 哈系冲突对系统的影响小


线性探针:

  • 使用更少的内存,因为没有链表

  • 更好的缓存性能


哈希函数的改进


目前已经实现了很多不同的哈希算法。


双值哈希:

一个哈希函数返回两个哈希值,插入元素时插入到较短的链条上。这种方法能够减少链条长度的期望值。


双重哈希:

使用线性探针方法,但是每次冲突之后跳过不同数量的元素来寻找空位。这种方法能够很好地消除连续的占位,使得哈希表能够被几乎填满,但是删除很难实现。


Cuckoo哈希:

先产生一个哈希,计算出一个位置,如果有冲突,再增加一些参数继续哈希,计算出另外一个位置。直到找到空位位置。这种方法的查找操作在最坏情况下复杂度是N。


哈系表和二叉树的比较


哈希表和平衡树都可以实现关联数组。


哈希表:

  • 代码简单

  • 这种方法的性能最好

  • 对于简单的关键字来说这种方法更快

  • 在java中有更好的系统支持,比如String中使用了hashCode的缓存


二叉树:

  • 更好的性能保障

  • 支持顺序操作(获取某个元素的名次、第N大的值、X到Y的元素数量等)

  • compareTo函数比hashCode函数更容易实现,不容易出错


Java库中对于这两种方法都有实现。java.util.TreeMap  java.util.TreeSet是通过红黑树实现的,java.util.HashMap  java.util.IdentityHashMap是通过哈希表实现的。