- 没有考虑树的情况,只考虑链表的情况
- 建议先自己看一下resize源码,琢磨之后再来看博客
- 这个方法中包含了对哈希表的扩容(或者初始化),把原来哈希表中的元素搬到新哈希表中去,把原来链表上的各个元素也搬到新的哈希表去
- 有个结论,原来的节点,无论是哈希表上的点还是链表上的点,在新哈希表中对应哈希表的下表只能是和原来下表相等或者是原来的下表加上原哈希表的长度
- 有一个前置知识点,哈希表中,同一个桶中的节点模哈希表的长度得到的余数是相同的,但是这些节点各自的哈希值不一定相同,也就是说,原来同一个桶中的节点,当哈希表的长度变为原来的两倍之后,他们模这个新长度得到的余数不一定是相等的,也就是他们不一定还会在同一个桶中
- e.hash & oldCap 这个东西要特别注意,他是一个推导出来的, 原式子应该是 e.hash & (cap-1) == e.hash&(2.cap-1) ,如果e.hash & oldCap == 0,那么等式成立,也就是说这个哈希在原来的哈希表长度和在扩容后的哈希表长度中的模出来的余数都是一样的,如果不等呢,e.hash & (cap-1) != e.hash&(2.cap-1),这个推导出来是1!=2
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];8
table = newTab;
if (oldTab != null) {
// 开始遍历哈希表
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
// 如果原来哈希表中的值不为空,将原来的值清空掉
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
// 如果原来这个桶只有一个节点,那么就把他放到新哈希表里去就好了,别的不用管
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
// 树节点,这里不讨论
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
// 这里说明当前节点是有链表在其后的
else { // preserve order
// 为什么需要定义两组头结点和尾节点呢? 前文说过,同一个桶中的节点在哈希表扩容之后对新哈希表取模得到的余数不一定相同
// 定义两组头尾节点是为了找到模旧长度和模新长度得到的余数一样和不一样的节点
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
// 这是最关键地方,前文有提到,通过这个判断可以判断节点应该在的位置
// 然后就把相同和不同的分别放到两组头尾节点下咯
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;
}
} while ((e = next) != null);
// 然后把两组节点放到新的哈希表里去,大功告成!
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;