resize 方法
数组为空或者元素数量超过阈值, 将会执行 resize()
方法, 结果是将数组的长度加倍
1 | final Node<K,V>[] resize() { |
复制旧链表到新数组
由于元素所在索引值是通过哈希值和数组长度减一进行与操作来得到, 当数组扩容一倍后, 元素在新数组的索引值就有两种情况, 一种是跟旧数组索引值一样, 另一种是旧数组索引值加旧数组长度.
举个例子, 因为数组长度一直都是 2 次幂, 那么假设旧数组长度是 10000, 减一是 1111, 新数组长度 100000, 减一就是 11111. 元素在旧数组的索引值是 xxxx, 在新数组的索引值便是 0xxxx 或者 1xxxx = xxxx + 10000(旧数组长度), 因此可以直接通过旧索引值+旧数组长度来得出新索引值.
那么现在的问题就是如何判断元素是在原来的索引位置还是新的索引位置, 其实就是判断哈希值的高位是 0 还是 1, 这里可以通过元素的哈希值和旧数组长度进行与运算得出, 如果结果是 0, 对应的就是原来位置, 非 0 对应新位置.
源代码用头尾节点来记录链表, 因为要生成两个链表, 所以有两对头尾节点, 如下:
1
2
3
4
5
6// 旧索引位置对应的头尾节点
Node<K,V> loHead = null, loTail = null;
// 新索引位置对应的头尾节点
Node<K,V> hiHead = null, hiTail = null;
// 记录下一个节点
Node<K,V> next;开始遍历旧数组链表, 源代码用了 do…while() 方式循环遍历, 如下:
1
2
3
4
5do {
next = e.next;
...
} while ((e = next) != null)判断该元素是放到旧索引位置还是新索引位置, 判断方法是上面所说的哈希值和旧数组长度做与运算, 如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 与运算为 0, 说明是旧索引位置
if ((e.hash & oldCap) == 0) {
// 尾节点是空, 说明这是第一个元素, 用头节点指向这个元素
if (loTail == null)
loHead = e;
// 否则就把这个元素连接到链表的最后
else
loTail.next = e;
// 最后尾节点指向链表新增的节点, 也是最后一个节点
loTail = e;
}
// 否则是新索引位置
else {
// 同上
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}当一个链表遍历完成了, 我们将得到两个新链表, 把这两个新链表保存到数组就完成了, 如下:
1
2
3
4
5
6
7
8
9
10
11// 尾节点不为空, 链表是存在的, 把头节点保存到新数组的旧索引值
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
// 把头节点保存到新数组的新索引值
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
如果有疑问欢迎来 Issues 探讨