final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
//先判断之前的容器容量是否大于0
/*
1、resize()函数在size > threshold时被调用。
oldCap大于 0 代表原来的 table 表非空, oldCap 为原表的大小,
oldThr(threshold) 为 oldCap × load_factor
*/
if (oldCap > 0) {//如果大于0
//如果之前的容量超出容器容量最大值,那么将临界值赋予最大值,直接返回旧容器对象
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//进行扩容,为之前的2倍,临界值也是一样
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
//容器大小小于0,则指定临界值为容器大小,这个临界值是之前初始化的
/*
2、resize()函数在table为空被调用。
oldCap 小于等于 0 且 oldThr 大于0,代表用户创建了一个 HashMap,但是使用的构造函数为
HashMap(int initialCapacity, float loadFactor) 或 HashMap(int initialCapacity)
或 HashMap(Map<? extends K, ? extends V> m),导致 oldTab 为 null,oldCap 为0,
oldThr 为用户指定的 HashMap的初始容量。
*/
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
/*
3、resize()函数在table为空被调用。
oldCap 小于等于 0 且 oldThr 等于0,用户调用 HashMap()构造函数创建的 HashMap,所有值均采用默认值,
oldTab(Table)表为空,oldCap为0,oldThr等于0,
*/
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//如果新制定的临界值为0重新指定
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
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
//如果扩容后,元素的index依然与原来一样,那么使用这个head和tail指针
Node<K,V> loHead = null, loTail = null;
//如果扩容后,元素的index=index+oldCap,那么使用这个head和tail指针
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
//这个地方很巧妙,直接通过e.hash & oldCap得出e在newTab中的位置;
//因为table是2倍扩容,所以只需要看hash值与oldCap进行与操作,结果为0,那么还是原来的index;否则index = index + oldCap
/*
通过与元素的hash值进行与操作,能够快速定位到数组下标
相对于取模运算,直接进行与操作能提高计算效率。在CPU中,所有的加减乘除都是通过加法实现的,而与操作时CPU直接支持的。
扩容时简化计算数组下标的计算量
因为数组每次扩容都是原来的两倍,所以每一个元素在新数组中的位置要么是原来的index,要么index = index + oldCap
*/
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;
//还是原来的index
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
//index 变为 index + oldCap
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
扩容是一个分厂消耗性能的工作,有些时候我们一直容量的情况下,尽量进行初始化,以避免扩容
而且在JDK1.8之前,扩容操作在多线程情况下很容易造成环形链表,如果get的话产生死循环的情况
在1.8中resize()方法不再调用transfer()方法,而是直接将原来transfer()方法中的代码写在自己方法体内;
当然表面上我们能看到方法的减少,其实还有一个重大改变,那就是:扩容后,新数组中的链表顺序依然与旧数组中的链表顺序保持一致!不再是之前的改变顺序了
之前是 a ->b->c 扩容后 c->b->a