数据结构——HashMap(内含图形演示)

本文解析了HashMap在Java 1.7与1.8版本中的不同,包括底层数据结构的变化、使用红黑树的原因、索引计算方式、put方法流程等关键信息,并通过图形演示帮助理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、底层数据结构,1.7 与 1.8 有何不同?

        ①1.7 数组 + 链表,1.8 数组 + (链表 |  红黑树)

细节:1.8的链表和红黑数是可以互相转换的,链表元素比较多的时候,链表就会转成红黑树,元素减少了,红黑树也可以退化成链表。

二、为何要用红黑树,为何一上来不树化,树化阈值为何是 8,何时会树化,何时会退化为链表?

①红黑树用来避免 DoS 攻击,防止链表超长时性能下降,树化应当是偶然情况

        ①hash 表的查找,更新的时间复杂度是 O1,而O(1),而红黑树的查找,更新的时间复杂度是 Olog2nO(log_2⁡n ),TreeNode 占用空间也比普通 Node 的大,如非必要,尽量还是使用链表。

        ②hash 值如果足够随机,则在 hash 表内按泊松分布,在负载因子 0.75 的情况下,长度超过 8 的链表出现概率是 0.00000006,选择 8 就是为了让树化几率足够小

②树化两个条件:链表长度超过树化阈值;数组容量 >= 64

③退化情况1:在扩容时如果拆分树时,树元素个数 <= 6 则会退化链表

④退化情况2:remove 树节点时,若 root、root.left、root.right、root.left.left 有一个为 null ,也会退化为链表

三、索引如何计算?hashCode 都有了,为何还要提供 hash() 方法?数组容量为何是 2 的 n 次幂?

        ①计算对象的 hashCode(),再进行调用 HashMap 的 hash() 方法进行二次哈希,最后 & (capacity – 1) 得到索引

        ②二次 hash() 是为了综合高位数据,让哈希分布更为均匀

        ③计算索引时,如果是 2 的 n 次幂可以使用位与运算代替取模,效率更高;扩容时 hash & oldCap == 0 的元素留在原来位置 ,否则新位置 = 旧位置 + oldCap

        ④但 ①、②、③ 都是为了配合容量为 2 的 n 次幂时的优化手段,例如 Hashtable 的容量就不是 2 的 n 次幂,并不能说哪种设计更优,应该是设计者综合了各种因素,最终选择了使用 2 的 n 次幂作为容量

四、介绍一下 put 方法流程,1.7 与 1.8 有何不同?

①HashMap 是懒惰创建数组的,首次使用才创建数组

②计算索引(桶下标)

③如果桶下标还没人占用,创建 Node 占位返回

④如果桶下标已经有人占用

        ①已经是 TreeNode 走红黑树的添加或更新逻辑

        ②是普通 Node,走链表的添加或更新逻辑,如果链表长度超过树化阈值,走树化逻辑

⑤返回前检查容量是否超过阈值,一旦超过进行扩容

⑥不同

        ①链表插入节点时,1.7 是头插法,1.8 是尾插法

        ②1.7 是大于等于阈值且没有空位时才扩容,而 1.8 是大于阈值就扩容

        ③1.8 在扩容计算 Node 索引时,会优化

五、加载因子为何默认是 0.75f

①在空间占用与查询时间之间取得较好的权衡

②大于这个值,空间节省了,但链表就会比较长影响性能

③小于这个值,冲突减少了,但扩容就会更频繁,空间占用多

六、图形演示及讲解

        1、快速查找演示:和ArrayList对比,查找a元素,list需要一个一个遍历,而hashmap获取原始hash值,二次hash,再计算出桶下标,就可以实现快速查找。如果查找c,因为s,c的桶下标一样,而且s是链表头,先要与s比较是否相等,才可以继续往下遍历。 

6098343e76b247c0b1d32202f1e5e410.png

        2、链表过程的一些解决思路:现在我们给插入的12345678固定他的hash,使其插入都同一个索引,这时候我们查找元素,复杂度从理想的O(1) -> O(n),所以我们得缩短链表。

0a3f4119ceea4bd395e47d1c52f3b9b5.png

         扩容思路1:链表扩容,容量是16,扩容因子是0.75,即元素个数到达13个,链表就会扩容。如下图,因为扩容之后桶下标重新计算,由原来的模16变成模32,链表长度缩短的。

c7439fbfb3904f9ea657e05d282c826d.png

        特殊情况:原始hash都一样,扩容之后桶下标也没有变化,所以链表不会缩短。

0e20f9a888e34321a79b2890a0b1da57.png

         扩容思路2:树化,但是需要满足两个条件。1、链表长度大于8,2、数组容量大于等于64

         细节:链表的长度是可能大于8的,因为如果原始码都一样,然而数组长度并没有到达64。另外提一句,树化的情况是极少出现的,算是异常的情况。如第二张图,将23万个英语单词放入HashMap,统计每种数量的情况。

 971b1ec4fd5c469fadc330bf98db6921.png

fd249ce373054a9e9e3b58273e815194.png

        3、退化情况,如下图,abc从链表脱离出来,长度小于6,退化成链表

dc9e285e0a664337b0d1ad95b545d0d4.png

7284d8b39d0844369b59fb3f0db81bea.png e06cdee9c5f74e12bb78e5507f8316c7.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

悠哉iky

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

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

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

打赏作者

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

抵扣说明:

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

余额充值