HashMap(3)進階篇--HashMap擴容機制
阿新 • • 發佈:2019-01-07
1.什麼是resize:
resize就是重新計算容量;當我們不斷的向HashMap物件裡不停的新增元素時,HashMap物件內部的陣列就會出現無法裝載更多的元素,這是物件就需要擴大陣列的長度,以便能裝入更多的元素;當然Java裡的陣列是無法自動擴容的,方法是使用一個新的陣列代替已有的容量小的陣列;就像我們用一個小桶裝水,如果想裝更多的水,就得換大水桶。
2.什麼時候需要resize():
當向容器新增元素的時候,會判斷當前容器的元素個數,如果大於等於閾值—即當前陣列的長度乘以載入因子的值的時候,就要自動擴容。
擴容:(原始碼 661-662)
計算閥值:
1.第一次建立Hash表時:
2.對HashMap進行擴容時:
3.原始碼分析:
final Node<K,V>[] resize() {
//儲存舊的 Hash 陣列
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
//超過最大容量,不再進行擴充
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//容量沒有超過最大值,容量變為原來的兩倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
//閥值變為原來的兩倍
newThr = oldThr << 1;
}
else if (oldThr > 0)
newCap = oldThr;
else {
//閥值和容量使用預設值
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
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"})
//建立新的 Hash 表
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
//遍歷舊的 Hash 表
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 {
//以連結串列形式存在的節點;
//這一段還是看下面的圖解吧,搞了好久才懂得 ^_^
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;
}
以連結串列形式存在的節點:
存在兩個數他們的 Hash 值分別為:5,21 二進位制形式分別為(0101,10101)。
若沒有進行擴容時容量為 16,進行擴容之後的容量為 32
座標點的計算(計算規則 :e.hash & (newCap - 1)):
沒有進行擴容時:
可以看到兩個Hash值所計算的座標是相同的。
進行擴容之後:
可以看出經過擴容之後,兩次計算的座標出現了不同,但是第二個座標點增加了 oldCap 個長度。
再看看 e.hash & oldCap 所計算出的結果:
可以看到當 e.hash & oldCap == 0 是,原來的座標沒有發生變化,e.hash & oldCap != 0 在原來座標的前提下增加 oldCap 。
兩條連結串列的連線過程:
在向表中連線的時候最後一個節點的下一個節點做空。
總結:
- 在對 HashMap 進行擴容時,閥值會變為原來的兩倍;
- 在對HashMap進行擴容的時候,HashMap的容量會變為原來的兩倍;
- 擴容是一個特別耗效能的操作,所以當程式設計師在使用HashMap的時候,估算map的大小,初始化的時候給一個大致的數值,避免map進行頻繁的擴容。
- 負載因子是可以修改的,也可以大於1,但是建議不要輕易修改,除非情況非常特殊。