1. 程式人生 > 實用技巧 >HashMap的擴容演算法

HashMap的擴容演算法

HashMap的擴容演算法

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    
    // 1.舊錶的容量不為0,即舊錶不為空
    if (oldCap > 0) {
        // 1.1 判斷舊錶的容量是否超過最大容量值:如果超過則將閾值設定為Integer.MAX_VALUE,並返回舊錶,
        // 此時oldCap * 2比Integer.MAX_VALUE大,因此無法進行重新分佈,只是單純的將閾值擴容到最大
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 1.2 將新表容量賦值為oldCap * 2,如果newCap<最大容量且oldCap>=16,則將新閾值設定為舊閾值的兩倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    
    // 2.如果舊錶的容量為0, 舊錶的閾值大於0, 是因為初始容量被放入閾值,則將新表的容量設定為舊錶的閾值
    else if (oldThr > 0)
        newCap = oldThr;
    else {
  // 3.舊錶的容量為0, 舊錶的閾值為0,這種情況是沒有傳初始容量的new方法建立的空表,將閾值和容量設定為預設值
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    // 4.如果新表的閾值為空, 則通過新的容量*負載因子獲得閾值
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
  // 5.將當前閾值設定為剛計算出來的新的閾值,定義新表,容量為剛計算出來的新容量,將table設定為新定義的表。
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    // 6.如果舊錶不為空,則需遍歷舊錶所有節點,將節點賦值給新表
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            
            // 如果索引值為j的舊錶頭節點不為空,則e指向該頭結點
            if ((e = oldTab[j]) != null) {  
                oldTab[j] = null; // 將舊錶的節點設定為空, 以便垃圾收集器回收空間
                
                // 7.如果e.next為空, 則說明舊錶的該位置只有1個節點。
                // 計算該節點在新表中的索引位置, 然後將該節點放在該位置
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                
                // 8.如果是紅黑樹節點,則進行紅黑樹的重hash分佈(跟連結串列的hash分佈基本相同)
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                
                // 9.如果是普通的連結串列節點,則進行普通的重hash分佈
                else {
                    
                    // 儲存“原索引位置”的節點
                    Node<K,V> loHead = null, loTail = null;
                    // 儲存“原索引位置+oldCap”的節點
                    Node<K,V> hiHead = null, hiTail = null; 
                    Node<K,V> next;
                    do {
                        next = e.next;
                // 9.1 如果e的hash值與舊錶的容量進行與運算為0,則擴容後的索引位置跟舊錶的索引位置一樣
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null) // 如果loTail為空, 代表該節點為第一個節點
                                loHead = e; 	// 則將loHead賦值為第一個節點
                            else
                                loTail.next = e;    // 否則將節點新增在loTail後面
                            loTail = e; 			// 並將loTail賦值為新增的節點
                        }
             // 9.2 如果e的hash值與舊錶的容量進行與運算為1,則擴容後的索引位置為:舊錶的索引位置+oldCap
                        else {
                            if (hiTail == null) 	// 如果hiTail為空, 代表該節點為第一個節點
                                hiHead = e; 		// 則將hiHead賦值為第一個節點
                            else
                                hiTail.next = e;    // 否則將節點新增在hiTail後面
                            hiTail = e; 			// 並將hiTail賦值為新增的節點
                        }
                    } while ((e = next) != null);
              // 10.如果loTail不為空(說明舊錶的資料有分佈到新表上“原索引位置”的節點),則將最後一個節點
              // 的next設為空,並將新表上索引位置為“原索引位置”的節點設定為對應的頭節點
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
              // 11.如果hiTail不為空(說明舊錶的資料有分佈到新表上“原索引+oldCap位置”的節點),則將最後
                 // 一個節點的next設為空,並將新表上索引位置為“原索引+oldCap”的節點設定為對應的頭節點
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    // 12.返回新表
    return newTab;
}

擴容過程中索引值的計算

中同一索引的所有節點進行重hash計算,得到的索引位置分佈在:原索引位置原索引位置 + 舊錶容量

這裡使用舊錶其容量為16新表其容量為32擴容過程中,展示節點A和節點B的索引的重新計算過程:

根據table表索引計算公式index = (n - 1) & hash計算舊錶中節點在新表中的索引值。

舊錶中節點A和節點B所在的索引位置:

擴容後節點A和節點B所在新表中的索引位置:

參考:https://blog.csdn.net/v123411739/article/details/78996181