HashMap 如何解决哈希冲突的?

HashMap 如何解决哈希冲突的?

了解 Hash 冲突,那首先我们要先了解 Hash 算法和 Hash 表。

Hash 算法,就是把任意长度的输入,通过散列算法,变成固定长度的输出,这个输出结果是散列值。

Hash 表又叫做“散列表”,它是通过 key 直接访问在内存存储位置的数据结构, 在具体实现上,我们通过 hash 函数把 key 映射到表中的某个位置,来获取这个位置的数据,从而加快查找速度。这里为什么快?

为速度而散列:
散列的价值便在于速度, 散列使得查询得以快速进行, 那么什么是数组链表,什么又是散列机制呢?

  正所谓数组链表, 便是定义一个数组, 然后数组的每一个成员都是一条链表,数组只需要记载这条链表的引用即可, 这样不需要直接在数组内部存储 键-值 对而需要大量的连续的内存空间.
散列机制便是所谓的 hashCode 方法返回的 int 数, 他是通过对象的信息(默认是地址),通过某种散列的数学函数生成的一串 int 数字.
  在了解了数组链表和散列机制后,我们再来想一想 HashMap 内部的 键-值 对是如何高效的维持的.

  首先, 仍然会定义一个数组, 这个数组的是一个存储链表的引用数组, 从而解决了数组因存储对象而需要大量的连续的内存空间的缺陷.
  然后, 我们在 put 一个元素的时候, 会调用 key 的 HashCode 方法生成一个散列码, 然后用这个散列码余上数组的容量,从而得到了一个数组的下标, 然后把这个 键-值 对存储在这个下标下对应的链表内.
在理想的情况下, 假设没有散列冲突(不同的对象产生了相同的散列码), 在我们用 key 去查询一个 value 的时候, 仍然用这个散列函数得到数组的下标, 从而直接获取了对应的 value, 这个效率简直了…提升了多少倍啊…
  可是不会产生冲突的散列函数是几乎不存在的, 于是乎便会出现不同的 key 产生了相同的散列码, 在我们查询的时候就得采用 equals 线性遍历这少部分的因冲突而存储在一个链表中的 键-值 对, 但是这和全部的元素进行线性遍历, 效率仍然是提高了很多倍。

所谓 hash 冲突,是由于哈希算法被计算的数据是无限的,而计算后的结果范围有限的,所以总会存在不同的数据经过计算后得到的值相同,这就是哈希冲突。

通常解决 hash 冲突的方法有 4 种:

开放定址法,也称为线性探测法,就是从发生冲突的那个位置开始,按照一定的次序从 hash 表中找到一个空闲的位置,然后把发生冲突的元素存入到这个空闲位置中。ThreadLocal 就用到了线性探测法来解决 hash 冲突的。

链式寻址法,这是一种非常常见的方法,简单理解就是把存在 hash 冲突的 key, 以单向链表的方式来存储,比如 HashMap 就是采用链式寻址法来实现的。

再 hash 法,就是当通过某个 hash 函数计算的 key 存在冲突时,再用另外一个hash 函数对这个 key 做 hash,一直运算直到不再产生冲突。这种方式会增加计算时间,性能影响较大。

建立公共溢出区,就是把 hash 表分为基本表溢出表两个部分,凡事存在冲突的元素,一律放入到溢出表中。

HashMap 在 JDK1.8 版本中,通过链式寻址法+红黑树的方式来解决 hash 冲突问题,其中红黑树是为了优化 Hash 表链表过长导致时间复杂度增加的问题。当链表长度大于 8 并且 hash 表的容量大于 64 的时候,再向链表中添加元素就会触发转化。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

皮皮攻城狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值