「这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战」
- 前言
我们平常使用HashMap 的时候 ,应该使用比较多的就是 put 和 get 方法,好学的小伙伴,点进去 put 方法的源码,会发现在某些情况下 它会调用 resize()。此外,我们还可以发现在HashMap 中其他地方 调用这个 resize()方法的 也不少。那我们就来讨论下这个方法主要是干啥的。
官方翻译:初始化或加倍表的大小。如果为空,则按照字段阈值中持有的初始容量目标分配。否则,因为我们使用的是2的幂展开,所以每个容器中的元素必须保持在相同的索引,或者在新表中以2的偏移量幂移动。
- 源码
源码还是比较长,我们还是一段一段的看
首先说一下,这些hashmap 自带的属性,和静态常量的含义我在前面 java HashMap 详解 – (默认常量和构造函数) 中已经讲过了,不懂的可以去看看。
2.1 第一部分
- 定义一个变量 oldTab,保存原先 hashMap 中的 table。
- 定义变量 oldCap,保存 oldTab 的长度(如果 oldTab == null ,则返回0)
- 定义变量 oldThr,保存 原先的 threshold
- 定义变量 newCap(保存 扩容后的table 的长度);newThr(扩容后的 threshold 的值),先把他们两初始化为0
- 当 oldCap > 0 时
+ 如果 oldCap >= MAXIMUM\_CAPACITY,则说明无法扩容了,则把 threshold置为 Integer.MAX\_VALUE;并返回 oldTab;
+ 或者当,newCap = oldCap*2 且 newCap < MAXIMUM\_CAPACITY(最大容量) && oldCap >= 默认初始化容量 16; newThr = oldThr << 1(即 newThr = 2* oldThr),即扩容两倍
- 或者当 oldThr > 0 ,说明它初始化容量时设置过阈值,则直接把 oldThr 赋给 newCap 即可。
- 否则 则按默认的初始化规则,newCap = 16; newThr = 0.75*16 (进到这里则说明,原先的map,初始化时没有指定 initialCapactity 大小,所以没有给 threshold 赋值,即它为 0)
- 当 newThr == 0时,则重新计算 newThr的值
+ 用 newCap \* 负载因子,得到 float 类型的变量 ft,当(newCap和ft均小于 MAXIMUM\_CAPACITY )时,把ft 赋给 newThr,否则把 Integer.MAX\_VALUE 赋给 newThr。
- 处理完后,把 newThr 赋给 threshold.
2.2 第二部分
- 定义一个新表 newTable,它的长度就是扩容后的长度。
- 使当前table 指向这个newTable
- 当旧表不为null (这里主要考虑数据复制)
- 遍历旧表,且当该元素不为null时 进入if判断
- 当 e.next == null ,说明当前这个节点就只要它一个元素,我们只需要把它重新分配位置即可,(位置的具体计算方式,元素的hash值 & 扩容后的table长度-1 ),因为前面说过newCap 是2的幂数,所以newCap-1 的二进制形式是(1,11,111,1111 …)这种形式的,与当前元素hash值去 &运算,最终结果就是 0—(newCap-1),刚好保证随机分配到hashMap的各个位置中去。
- 或者,当该节点是一个 treeNode类型,则说明这个节点下的元素已经树华,会将树容器中的节点拆分为较低的树容器和较高的树容器,如果太小则取消树化,变成链表。具体可以看 java HashMap 详解 – (默认常量和构造函数) ,后续这个方法我在细讲。
- 否则的话说明这个节点下面是一个链表。
- lohead : 低位首节点, loTail : 低位尾节点
- hiHead : 高位首节点, hiTail : 高位尾节点 , next : 保存下一节点。
- 遍历链表,当 e = next不为null,一直遍历
- 首先用next 保存 e.next
- 这里 使用的是 : e.hash & oldTab的长度 (前面说过 oldTable 的长度 也是2的幂数,所以这里的二进制形式是 0,10,100,1000,10000….)当它的结果为0 时,则说明它的hash值小于老数组的长度,即他再次分配还是会在原先老数组的位置上(即 低位数组)。所以如果低位尾结点为null ,就把它赋给 低位头结点,否则就赋给低位尾结点的next。最后使得 低位尾结点 = e。
- 这里 当 e.hash & oldTab的长度 不为0,则说明这个元素分配的位置必在,新数组的高位上,所以当高位尾结点为null,直接把该元素赋给高位头结点,否则赋给高位尾结点的next,最后把高位尾结点元素置为 e。
- 当低位尾结点不为null,则把 它的next 置为null,把它的头结点直接放到,新数组索引为j的位置上。
- 当高位尾结点不为null,也把它的 next 置为 null,因为它是高位,索引把它放到 新数组索引 j+oldCap的位置上
- 最后返回这个新数组即可。
本文转载自: 掘金