HashMap put方法流程

基于jdk11

首先计算hash值,(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16),可以看出来key是可以为null的,调用hashcode()之后得到32位int类型的值,然后和该值的高16位进行异或操作,为什么要这样操作呢?因为后面计算数组的下标的时候对数组的长度进行了取模(当然代码中是用的&操作,结果是一样的,只是&的性能更高),一般数组的长度不会很大,16位已经是65535了,所以对于一个32位的hashcode值只有底16位参与了数组下标计算,这样很容易冲突,如果高16位也参与运算,这个冲突的概率会降低很多。
为什么是 ^ 运算, 而不是 & 或者 | 运算呢? 因为 | 的运算结果趋近于0 而 & 运算的结果趋近于 1, ^ 运算结果更加均匀。

计算完hash之后可以看到下面一共三个条件分支
在这里插入图片描述
调用HashMap的构造方法时并不会初始化Node[],而是等到放第一个值的时候才初始化。

假设现在存放一个Integer类型的键值对 <1,2>, 此时Node[] 还没有初始化,进入第一个条件分支 调用 resize()方法进行初始化Node[]和其他值,

在这里插入图片描述很明显此时oldTab = null 并且oldCap =0,因此进入688行的条件分支:newCap就是数组的长度:static final int DEFAULT_INITIAL_CAPACITY = 1 << 4,newThr就是是否扩容的门槛: 数组长度* 装载因子。
然后692行条件不满足,因此执行697行的代码,** threshold=newThr = 16 *0.75 =12 ** ,注意 ** threshold ** 这个是全局变量,后面要用到。并且在699行初始化了Node数组,长度是16。 701行条件不满足, 该方法结束。

继续执行putval方法,此时625行的条件满足,i = (n - 1) & hash 计算下标,和取模的结果一样,只是性能更高,此时在<1,1> 放在了Node[1]处。

然后执行++modCount; 和 ++size,getSize()方法返回的就是这个size的值,第一次put结束结束。

第二次put <17,17>,因为 17最终计算的下标和 前面放的<1,1>的下标是同一个。同样第一步计算hash值之后 进入putval方法,此时满足627行条件:
在这里插入图片描述

由625行的到p就是 <1,1> ,然后进入634行,满足636行条件,<17,17>挂在了<1,1>下面此时底层结构应该是这样的:
在这里插入图片描述

执行++modCount; ++size

然后分别put <163+1, 163+1>, <164+1,164+1> … 一直到 <168+1, 168+1>,此时链表长度是8 ,此时638行的条件成立 链表转化为红黑树。
此时容器中一共8个元素,继续添加 Node<2,2>,Node<18,18>,Node<34,34>, ok 11个元素了, 再添加一个Node<50,50>, 12个元素了,执行到657行 ++ size 的值是12, threshold的值多少呢,在第一次初始化的时候已经计算过了,上面resize()方法的690行 ,threshold =16 *0.75= 12,好了 657行代码条件成立,进行resize() 扩容,
在这里插入图片描述
可以计算出 变量 oldCap = 16,oldThr = 16 ,因此进入677行分支,全局变量static final int MAXIMUM_CAPACITY = 1 << 30,682行else if条件成立,得到 变量 newThr =oldThr << 1 = 16 <<1 = 32。再往下走 全局变量 threshold =newThr =32,699行创建新的长度为32的数组 newTab,然后进入701行,遍历旧的数组 将数据重新装入到新的数组newTab中,这里有个很精妙的计算 716行的if ((e.hash & oldCap) == 0) ,下面解释一下为什么可以这样算扩容后的坐标。

上面putVal 方法中底625行的 i = (n - 1) & hash 是在计算坐标,扩容后的坐标应该是 i_new=(2n-1) & hash ,假设 2的x次幂 = n, 无论是 (n-1) 还是 (2n-1) 的二进制 从第1位到 第(x-1)位 都是1,因此 在 和 hash做 & 操作的时候 ,第1位 到第(x-1)位的结果都是一样的,因此 只看 第 x位是0还是1 ,如果1 则 新下标i_new = oldCap +i, 如果是0,则i_new =i; 假设 oldCap=16 , hash =17 , 17的二进制是 00010001 , 此时 坐标 i =1 , 扩容后newCap =32, 2的4次幂=16(即x =4) ,第四位是1 ,因此17= oldCap+i =17,在新数组中的坐标是17, 再用第 625行的代码计算一下 31 & 17 =17 ,可见是正确的。也就是说扩容之后某个Node的坐标只有两种可能 即 原来的i 和oldCap + i 。
那么如何判断新坐标是i还是 oldCap+i呢, 对于某一个hash ,hash = n * oldCap + i ,其中0<= i <oldCap。如果n是奇数则 i_new = oldCap +i, 如果n 是偶数,则i_new = i ,利用取模(前面已经说过 取模和 & 的结果是一样的 只是性能不一样)可以来证明这个结论: 当n是偶数时, hash = k * newCap+i 可得 hash %newCap = (knewCap +i) %newCap = i;当n是奇数时,hash = (2k+ 1) * oldCap +i = k * newCap + oldCap + i 可得 hash%newCap=(knewCap + oldCap +i) =oldCap +i。 证明完毕。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值