Hash冲突的解决方案

总结
  • 拉链法:冲突位置挂链表,链表过长转红黑树,HashMap 用的这个
  • 开放地址法:冲突时在数组内找下一个空位,ThreadLocalMap 用的线性探查
  • 再哈希法:冲突就换个哈希函数重新算,简单但开销大

1. 拉链法(链接法)

Hash 碰撞就是两个 key 算出来的 hash % 数组长度 一样,落到了同一个槽位。

拉链法的处理方式是:冲突了就在这个槽位挂一条链表,所有冲突的元素都串在上面。查找时定位到槽位,再遍历链表找到对应的 kv。

链表太长的话遍历性能会退化到 O(n),所以 HashMap 在链表长度超过阈值后会把链表转成红黑树,复杂度降到 O(log n)。

2. 开放地址法

开放地址法的核心思路是:所有元素都存在数组里,冲突了就在数组内部找另一个空位放。数组放不下时触发扩容。

有三种探查方式:

2.1 线性探查

冲突时依次往后找,步长固定为 1,直到找到空位或遍历完整个表。

di = 1, 2, 3, ..., m-1

实现最简单,但容易产生"聚集"问题——冲突元素扎堆,越冲越慢。ThreadLocalThreadLocalMap 就是用的这个策略。

2.2 二次探查

冲突时左右跳跃式探测,步长按平方递增,分布更分散。

di = 1², -1², 2², -2², ..., k², -k²  (k ≤ m/2)

2.3 伪随机探查

步长用伪随机数序列,每次加上一个随机偏移量,进一步打散聚集。

di = 伪随机数序列,如 i = (i + p) % m

3. 再哈希法

冲突了就换一个哈希函数重新算,直到落到空位为止。

思路简单,但每次冲突都要重新计算哈希,冲突多的时候开销比较大,实际用得不多。