1. 程式人生 > >HashMap(3)進階篇--HashMap擴容機制

HashMap(3)進階篇--HashMap擴容機制

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 。

兩條連結串列的連線過程:

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

在向表中連線的時候最後一個節點的下一個節點做空。

總結:

  1. 在對 HashMap 進行擴容時,閥值會變為原來的兩倍;
  2. 在對HashMap進行擴容的時候,HashMap的容量會變為原來的兩倍;
  3. 擴容是一個特別耗效能的操作,所以當程式設計師在使用HashMap的時候,估算map的大小,初始化的時候給一個大致的數值,避免map進行頻繁的擴容。
  4. 負載因子是可以修改的,也可以大於1,但是建議不要輕易修改,除非情況非常特殊。