扩容时机:元素数量 > 容量 × 负载因子
调用resize()进行扩容
注意:新建 HashMap 不初始化数组,第一次 put 时,resize 才会创建数组(懒加载)。
意思就是:
即使不扩容,而是第一次创建数组后第一次 put,也是用 resize 实现的
resize全流程
1.判断旧数组是否存在
- 旧数组为
null(首次 put):旧容量 = 0,新容量 = 默认 16,新阈值 = 16×0.75=12 - 旧数组不为 null:
- 旧容量
oldCap左移一位newCap = oldCap << 1(翻倍) - 旧阈值
oldThr左移一位newThr = oldThr <<1 - 上限:最大容量
MAX_CAPACITY = 1 << 30,超过就不再扩容,阈值设为Integer.MAX_VALUE
- 旧容量
1.旧容量即原数组长度;旧阈值即0.75原倍数组长度
2.<<是左移运算符:二进制整体往左挪 1 位,右边补 0,等价 乘以 2,这也是为什么每次扩容都是加倍
3.MAX_CAPACITY = 1 << 301 << 30:1 左移 30 位 = 2³⁰ = 1073741824,HashMap 允许数组最大长度就是这个。
2.创建新数组
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
1.Node<K,V>是这里面的节点类2.把
table引用指向新数组(地址),旧数组保留临时引用oldTab,准备迁移元素3.不应该是Node<K,V>\[\] newTab =new Node<K,V>newCap;吗
语法上想表达的意思是对的,但是 Java 不允许直接这样写!(反正有ai了现在无所谓)
3. 遍历旧数组,迁移每一个桶(核心)
用遍历其中每个位置下的链表吗?看下面每一种情况你就知道了
- 情况①:
oldTab[i] == null→ 直接跳过,无元素 - 情况②:当前桶只有单个节点(无链表 / 红黑树)
- 直接计算新下标:
newIndex = hash & (newCap -1),放到新数组对应位置
- 直接计算新下标:
- 情况③:桶内是链表 (多个 Node,还没转红黑树) 尾插法!!
- jdk8不再重新hash

我们把在原位置的叫做低位,位置改变的叫做高位
- 情况④:桶内是红黑树
调用 split() 方法拆分红黑树,同样按 hash & oldCap 分成低位、高位两组。

补充:
因为扩容后放的位置是(table - 1)& hashCode
而hashCode是由key根据固定公式求的,所以不会变
并且16=>二进制:10000 => -1 = 01111
扩容后: 100000 => -1 = 011111
与运算举例子(不同hash值举例):
eg1. 00101 => 分别和新老table-1值作与运算 => 00101、00101
eg2. 10101 => 分别和新老table-1值作与运算 => 00101、10101
eg3. 01100 => 分别和新老table-1值作与运算 => 01100、01100
eg4. 11100 => 分别和新老table-1值作与运算 => 01100、11100
扩容了,但是做与运算需要数组长度 - 1,所以总的来说相当于前面多了一位 1,现在要和 hashcode 做与运算,只需要看第五位不就行了,第五位扩容前为零,扩容后和原 hashcode 值相同 得到扩容后的下标
(五是因为这里仅仅拿16 -> 32 举例)
所以就是 hashcode 的第五位,如果是 0,那么下标不变(rehash后还在原位置 ),如果是 1,则下标改变(新下标 = 原下标 i + oldCap )自己多想一下为什么+ oldCap
