關於HashMap resize()方法

final Node<K,V>[] resize() {
    // 老的bucket 桶
    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; // double threshold
    }
    // 用於帶參數的初始化HashMap,因爲你有參數,在構造函數就已經賦值了 初始化容器大小、加載因子、臨界值
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        // 無參的,需要賦值默認的參數
        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"})
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; // 初始化新的Node數組
    table = newTab;
    if (oldTab != null) {
        // 遍歷老數組
        for (int j = 0; j < oldCap; ++j) {
            // 這裏的e特殊說明下,就是老數組 下標相同的鏈表元素
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                // 如果沒有next,直接通過這個元素的hash和新數組長度減一 獲取新的數組下標
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                // 如果是二叉樹,需要把樹分解
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                /**
                 * 如果有多個,就需要copy到新的表中
                 * 與1.7不同,1.7是每一個都rehash。
                 * 1.8 是根據根據一個鏈表,然後和老數組長度按位與。然後如果爲0,到新數組還是原來的位置。如果不爲0,位置就是之前的位置加老數組的長度作爲新的下標。
                 * 說一下爲什麼用 (e.hash & oldCap) == 0 判斷:
                 * 因爲在1.7的時候,每個鏈表(hash值相同)都是重新根據新數組(原數組大小的2倍)進行取模的,比較麻煩。但其實1.8的做法和rehash結果一樣,
                 * 但爲什麼1.8這樣做呢?因爲 數組的容量都是2的冪,數組容量減1得到的二進制位數要小於本來數組容量的位數(小1位 2^10 = 10000000000,2^10-1 = 1111111111 明顯少1位)
                 * 前提 oldCap是2的冪,所以二進制肯定只有一個1,其他爲0。那如果(e.hash & oldCap) == 0,e哈希值對應oldCap1的位置上的數肯定爲0。
                 * 那數組變爲2倍再減一,和之前的數組長度減一,就是最高位補了一個1(可以用15和31,31除以2 上15,餘1 然後這個1就把之前最前面那個1頂上去了),
                 * 補的這個1和原數組最高位那個1在一個位置。前面也說到了,e.hash 對應的那個位置是0.所以擴容了,位置還是原來的位置。
                 *
                  */
                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;
}

對於1.8舊數組轉新數組我畫個圖:

先記住 & 的前提是兩個爲1,纔是1.有一個爲0就是0.

圖裏面abcde代表e.hash,前面的就不需要管了,因爲數組容量最高位1前面都是0,與完了肯定是0。

假如老數組大小(oldCap)是16,減一是15,看上面的圖。位數是相差1位的(記住前面的高位都是0)。

那用e.hash和老數組長度按位與的時候,老數組因爲是2的冪,所以只有最高位那1個1。如果

(e.hash & oldCap) == 0,那說明老數組裏面的1對應e.hash位置肯定是0。

接着,數組的長度變爲兩倍,然後減一。就是之前數組減一的二進制前面補了一個1。補的這個1和之前數組最高位那個1是在一個位置(這是關鍵)。那我們知道e.hash 圖中a位置是0,那麼數組爲2倍減一 補的那個1和0與還是0。所以數組位置不變。

 

我寫的可能比較囉嗦,主要是爲了我和大家理解。你可以按照這個思路來考慮:1.8爲什麼不像1.7那樣重新哈希了,但1.8這樣搞和重新哈希結果是一樣的。那數組長度2倍減一和之前數組長度減一 就是在原來的基礎上最高位多了一個1。那我們怎麼知道e.hash 對應的那個位置是1還是0呢,是0反正原位置就不變了。那怎麼判斷呢?然後看人家用e.hash和原數組長度比,如果(e.hash & oldCap) == 0 得出原數組長度最高位那個1對應的e.hash那個位置是0.那爲0就不變了嗎?因爲這個0的位置就是數組長度2倍減一和之前數組長度減一比較高位多的那個1的位置。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章