一、哈希表针对冲突的两种方式优缺点
1.开放散列(open hashing)/ 拉链法(针对桶链结构)
1)优点:
①对于记录总数频繁可变的情况,处理的比较好(也就是避免了动态调整的开销)
②由于记录存储在结点中,而结点是动态分配,不会造成内存的浪费,所以尤其适合那种记录本身尺寸(size)很大的情况,因为此时指针的开销可以忽略不计了
③删除记录时,比较方便,直接通过指针操作即可
2)缺点:
①存储的记录是随机分布在内存中的,这样在查询记录时,相比结构紧凑的数据类型(比如数组),哈希表的跳转访问会带来额外的时间开销
②如果所有的 key-value 对是可以提前预知,并之后不会发生变化时(即不允许插入和删除),可以人为创建一个不会产生冲突的完美哈希函数(perfect hash function),此时封闭散列的性能将远高于开放散列
③由于使用指针,记录不容易进行序列化(serialize)操作
2.封闭散列(closed hashing)/ 开放定址法
1)优点:
①记录更容易进行序列化(serialize)操作
②如果记录总数可以预知,可以创建完美哈希函数,此时处理数据的效率是非常高的
2)缺点:
①存储记录的数目不能超过桶数组的长度,如果超过就需要扩容,而扩容会导致某次操作的时间成本飙升,这在实时或者交互式应用中可能会是一个严重的缺陷
②使用探测序列,有可能其计算的时间成本过高,导致哈希表的处理性能降低
③由于记录是存放在桶数组中的,而桶数组必然存在空槽,所以当记录本身尺寸(size)很大并且记录总数规模很大时,空槽占用的空间会导致明显的内存浪费
④删除记录时,比较麻烦。比如需要删除记录a,记录b是在a之后插入桶数组的,但是和记录a有冲突,是通过探测序列再次跳转找到的地址,所以如果直接删除a,a的位置变为空槽,而空槽是查询记录失败的终止条件,这样会导致记录b在a的位置重新插入数据前不可见,所以不能直接删除a,而是设置删除标记。这就需要额外的空间和操作。
3.哈希表相对于其他数据结构的优缺点
1)优点:
记录数据量很大的时候,处理记录的速度很快,平均操作时间是一个不太大的常数
2)缺点:
①好的哈希函数(good hash function)的计算成本有可能会显著高于线性表或者搜索树在查找时的内部循环成本,所以当数据量非常小的时候,哈希表是低效的
②哈希表按照 key 对 value 有序枚举(ordered enumeration, 或者称有序遍历)是比较麻烦的(比如:相比于有序搜索树),需要先取出所有记录再进行额外的排序
③哈希表处理冲突的机制本身可能就是一个缺陷,攻击者可以通过精心构造数据,来实现处理冲突的最坏情况。即:每次都出现冲突,甚至每次都出现多次冲突(针对封闭散列的探测),以此来大幅度降低哈希表的性能。这种攻击也被称为基于哈希冲突的拒绝服务攻击(Hashtable collisions as DOS attack)
// 好的哈希函数是指产生的哈希值是均匀(uniform)分布的,即可均匀分布在桶数组中
// 最坏的情况下插入数据被称作哈希表的退化(degenerate)
延伸阅读:
二、load factor
一个评估哈希表的关键统计数据,被定义为:load factor = n / k, n 是记录的数量,k 是桶的数量。
1)随着负载因子的扩大,出现冲突的概率会越来越大,所以当超过一定阈值时,需要扩容,避免哈希表因为频繁处理冲突而越来越慢;
2)随着负载因子的缩小,桶数组中空着的槽就越来越多,所以当小过一定阈值时,需要缩容,避免空槽飙升导致的内存浪费。