HashMap 源碼淺析(1.7和1.8)

Jdk 1.7

  1. 數據結構

    1.7版本的HashMap採用數組加鏈表的方式存儲數據,數組是用來存儲數據的在數組的位置,鏈表則時用來存放數據的,由於根據hash可能發生碰撞,一個位置會出現多個數據,所以採用鏈表結構來存儲數據,結構如下圖所示.

    HashMap 源碼淺析(1.7和1.8)

  2. 基本成員變量
    capacity 數組的長度

    // 當前數組的容量,始終保持2^n,可以擴容,擴容後是當前線程的2倍
        // 1 << 4 = 1 * 2^4   1的二進制左移4位
        static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    capacity 的最大值 (擴容時,如果已經是最大值,會設置成Integer.MAX_VALUE)

    // 如果傳入的值大於該值,也會替換爲 1 << 30(2 ^ 30)
        static final int MAXIMUM_CAPACITY = 1 << 30;

    factor 負載因子(用來算閾值)

    // 負載因子 默認值爲 0.75
        static final float DEFAULT_LOAD_FACTOR = 0.75f;

    threshold 閾值(capacity * factor),擴容時用來判斷有沒有大於等於這個值
    int threshold;

    size

    // map的容量
        transient int size;

    Entry (存儲數據的地方)

    static class Entry<K,V> implements Map.Entry<K,V> {
        // 就是傳輸key
        final K key;
        // 就是value
        V value;
        // 用於指向單項鍊表的下一個Entry
        Entry<K,V> next;
        // 通過key計算的hash值
        int hash;
    
        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
  3. 構造方法
    有參構造

    public HashMap(int initialCapacity, float loadFactor) {
                    // 容量不能小於0
                    if (initialCapacity < 0)
                            throw new IllegalArgumentException("Illegal initial capacity: " +
                                            initialCapacity);
                    // 容量大於MAXIMUM_CAPACITY時,等於MAXIMUM_CAPACITY
                    if (initialCapacity > MAXIMUM_CAPACITY)
                            initialCapacity = MAXIMUM_CAPACITY;
                    // loadFactor不能小於等於0
                    if (loadFactor <= 0 || Float.isNaN(loadFactor))
                            throw new IllegalArgumentException("Illegal load factor: " +
                                            loadFactor);
    
                    this.loadFactor = loadFactor;
                    threshold = initialCapacity;
                    init();
                }

    無參構造
    // 使用默認的容量和負載因子

    public HashMap() {
                        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
                }
  4. 基本方法
    Put方法 (具體流程看下面的執行流程分析或者代碼註釋)
    具體執行流程:
    (1) 判斷當前table是否爲EMPTY_TABLE={},證明沒有初始化,調用inflateTable初始化,具體詳見後面inflateTable()方法代碼分析.
    (2) 判斷key是否爲null,是null調用putForNullKey插入方法(證明1.7的HashMap允許key爲null),具體詳見後面putForNullKey()方法代碼分析.
    (3) 獲取當前key的hash,然後算出hash在數組的位置i(hash & (tab.length - 1)).給大家解釋下爲什麼數組的長度必須是2的冥,是和算i的位置有關係,因爲如果一個數是2的冥次方,假如這個數是n,那麼 hash % n = hash & (n -1),這就是爲什麼i的位置一定會在數組長度範圍中,因爲取得是餘數,還有就是位運算比直接取餘效率高.
    (4) 判斷當前位置上有沒有值table[i],如果有值,遍歷鏈表,找出相同的key和hash,然後替換value,返回舊的value(oldOvalue).
    (5) 如果沒有找到相同的key和hash,那麼就添加這個節點(Entry),方法addEntry().
    (6) 在addEntry()方法裏面判斷需不需擴容,需要就擴容,調用擴容方法resize(),然後在調用 createEntry()方法添加節點,size++.

            // 插入
            public V put(K key, V value) {
                    // 當插入第一個元素時,需要初始化
                    if (table == EMPTY_TABLE) {
                            // 初始化
                            inflateTable(threshold);
                    }
                    // key爲null是
                    if (key == null)
                            // 找出key爲null,替換返回舊值
                            // 沒有則新添加一個key爲null的Entry
                            return putForNullKey(value);
                    // 計算hash值
                    int hash = hash(key);
                    // 根據hash,找出table的位置
                    int i = indexFor(hash, table.length);
                    // 因爲在table[i]中,可能存在多個元素(同一個hash),所以要基於鏈表實現
                    // 循環table[i]上的鏈表(不爲空),存在就修改,返回舊值(oldValue)
                    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
                            Object k;
                            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                                    V oldValue = e.value;
                                    e.value = value;
                                    e.recordAccess(this);
                                    return oldValue;
                            }
                    }
    
                    modCount++;
                    // 爲空或者不存在,則新添加(需要計算容量)
                    addEntry(hash, key, value, i);
                    return null;
            }

    inflateTable初始化方法 (懶加載,只有第一次調用put方法時才初始化)

                // 初始化table
                private void inflateTable(int toSize) {
                        // Find a power of 2 >= toSize
                        // 計算出大於等於toSize最鄰近的2^n(所以capacity一定是2^n)
                        int capacity = roundUpToPowerOf2(toSize);
                        // 在此計算閾值 capacity * loadFactor
                        threshold = (int) Math.min(capacity * loadFactor, 
                        MAXIMUM_CAPACITY + 1);
                        // 創建capacity大小的capacity數組就是hashmap的容器
                        table = new Entry[capacity];
                        initHashSeedAsNeeded(capacity);
                }

    putForNullKey方法(存儲key爲null的數據)
    具體執行流程:
    (1) 遍歷table[0]處的鏈表(說明nullkey永遠存在table[0]位置)
    (2) 找到key==null 的數據,替換value,返回舊的value
    (3) 沒有找到,就在table[0]位置添加一個key爲null的Entry,調用addEntry()方法.

        private V putForNullKey(V value) {
                        // 遍歷table[0]的鏈表
                        // 找到key等於null的,把值覆蓋,返回舊值(oldValue)
                        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
                                if (e.key == null) {
                                        V oldValue = e.value;
                                        e.value = value;
                                        e.recordAccess(this);
                                        return oldValue;
                                }
                        }
                        modCount++;
                        // 沒有找到就添加一個key爲null的Entry
                        addEntry(0, null, value, 0);
                        return null;
                }

    addEntry方法(判斷是否需要擴容,然後在添加節點Entry)
    執行流程:
    (1) 判斷是否需要擴容,size(每次添加一個entry size++)>=threshold(閾值)並且當前這個key的hash算出的位置必須有元素才擴容,具體詳解看代碼註釋.
    (2) 如果滿足擴容條件,調用擴容方法resize(2 * table.length),table長度擴大2倍,然後重新算當前key的hash和位置bucketIndex.
    (3) 調用createEntry()方法,添加節點.

                // 添加節點到鏈表
                void addEntry(int hash, K key, V value, int bucketIndex) {
                        /*
                        * 擴容機制必須滿足兩個條件
                        * (1) size大於等於了閾值
                        * (2) 到達閾值的這個值有沒有發生hash碰撞
                        *  所以閾值在默認情況下是12 是一個重要節點
                        *  擴容範圍是12-27
                        *  最小12進行擴容,最大27時必須進行擴容
                        *  分析最小12擴容
                        *   當size是12時,判斷有沒有hash碰撞,有擴容,沒有繼續不擴容.
                        *   分析最大27擴容
                        *   當12沒有進行擴容時,size大於閾值就一直滿足了
                        *   就只需要判斷接下來的hash有沒碰撞,有就擴容,沒有就不擴容
                        *   最大是一種極端情況,前面11個全部在一個table索引上,接下來
                        *   15個全部沒有碰撞,11+15=26,table所有索引全部有值,在插入一個
                        *   值必須碰撞就是26+1=27最大進行擴容
                        * */
                        if ((size >= threshold) && (null != table[bucketIndex])) {
                                // 擴容(方法裏面重點講)
                                resize(2 * table.length);
                                // 計算hash,null時爲0
                                hash = (null != key) ? hash(key) : 0;
                                // 計算位置
                                bucketIndex = indexFor(hash, table.length);
                        }
    
                        createEntry(hash, key, value, bucketIndex);
                }

    createEntry方法(在傳入位置加入一個節點)

    // 創建一個新的Entry,放在鏈表的表頭,size++
                void createEntry(int hash, K key, V value, int bucketIndex) {
                        // 這裏可以理解爲當前的第一個節點
                        Entry<K,V> next = table[bucketIndex]; 
                        // 創建一個新的節點,next節點是當前的第一個節點,然後設置到bucketIndex位置
                        table[bucketIndex] = new Entry<>(hash, key, value, next); 
                        size++;
                }

    resize方法(擴容方法,擴容成原來的2倍)
    執行流程:
    (1) 計算oldTable的長度,如果oldTable的長度已經是最大值了,那麼就把閾值設置成Integer.MAX_VALUE,return.
    (2) 根據新的容量創建table.
    (3) 調用transfer方法轉移數據.
    (4) 將新table賦值給舊table,重新就算閾值.

        void resize(int newCapacity) {
                        Entry[] oldTable = table;
                        int oldCapacity = oldTable.length;
                        // 如果當前值已經是最大值了(2^30),就設置閾值爲Integer的最大值
                        if (oldCapacity == MAXIMUM_CAPACITY) {
                                threshold = Integer.MAX_VALUE;
                                return;
                        }
    
                        // 根據傳入Capacity重新創建新數組,擴容完成
                        Entry[] newTable = new Entry[newCapacity];
                        // 把原來的數據遷移到新的table(newTable)
                        transfer(newTable, initHashSeedAsNeeded(newCapacity));
                        // 將table設爲新table(newTable)
                        table = newTable;
                        // 設置新的閾值
                        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
                }

    transfer方法(負載轉移數據,把舊table的數據遷移到新table,至此擴容完成)
    注意:擴容完成後鏈表的順序會反轉,如下圖解釋.
    HashMap 源碼淺析(1.7和1.8)

                // 擴容之後遷移數據(重新計算hash,分配地址),很耗性能
                // 順便提一下jdk7(get死循環)就是擴容時造成,造成環形鏈表
                void transfer(Entry[] newTable, boolean rehash) {
                        // 新數組的容量
                        int newCapacity = newTable.length;
                        // 遍歷原table
                        for (Entry<K,V> e : table) {
                                // 輪詢e不等於null
                                while(null != e) {
                                        // 保存下個元素
                                        Entry<K,V> next = e.next;
                                        if (rehash) {
                                                // 計算出key的hash
                                                e.hash = null == e.key ? 0 : hash(e.key);
                                        }
                                        // 計算出table的位置
                                        int i = indexFor(e.hash, newCapacity);
                                        e.next = newTable[i];
                                        newTable[i] = e;
                                        e = next;
                                }
                        }
                }

    get方法(通過key獲取數據)
    執行流程:
    (1) 判斷key是否爲null,爲null調用getForNullKey()方法
    (2) 不爲null,調用getEntry方法

            // get方法
                public V get(Object key) {
                        // key等於null
                        if (key == null)
                                return getForNullKey();
                        // 不爲null是查找
                        Entry<K,V> entry = getEntry(key);
    
                        return null == entry ? null : entry.getValue();
                }

    getForNullKey()方法(遍歷table[0]位置數據,找到key==null的返回)

             private V getForNullKey() {
                        // 沒數據
                        if (size == 0) {
                                return null;
                        }
                        // 從table[0]處遍歷鏈表,找到key=null的返回
                        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
                                if (e.key == null)
                                        return e.value;
                        }
                        return null;
                }

    getEntry()方法(根據hash算出位置,遍歷當前位置的數據,找到key和hash相同的返回)

        final Entry<K,V> getEntry(Object key) {
                        // 沒數據
                        if (size == 0) {
                                return null;
                        }
                        // 獲取hash
                        int hash = (key == null) ? 0 : hash(key);
                        // 獲取table的位置,找到hash和key相同的返回
                        for (Entry<K,V> e = table[indexFor(hash, table.length)];
                                 e != null;
                                 e = e.next) {
                                Object k;
                                if (e.hash == hash &&
                                                ((k = e.key) == key || (key != null && key.equals(k))))
                                        return e;
                        }
                        return null;
                }

    remove()方法

            final Entry<K,V> removeEntryForKey(Object key) {
                        // 沒數據
                        if (size == 0) {
                                return null;
                        }
                        // 獲取hash
                        int hash = (key == null) ? 0 : hash(key);
                        // 計算位置
                        int i = indexFor(hash, table.length);
                        // 獲取i位置的entry
                        Entry<K,V> prev = table[i];
                        Entry<K,V> e = prev;
    
                        // 遍歷鏈表
                        while (e != null) {
                                Entry<K,V> next = e.next;
                                Object k;
                                // 找到了hash和key相等的
                                if (e.hash == hash &&
                                                ((k = e.key) == key || (key != null && key.equals(k)))) {
                                        modCount++;
                                        // 容量減減
                                        size--;
                                        // 說明是第一個元素
                                        // 把頭結點設置成他的下一個元素
                                        if (prev == e)
                                                table[i] = next;
                                        // 刪除當前e,把上一個元素的next指向當前e.next
                                        // 1 -2 -3-null 刪除2,把1的next指向2的next,就是1-3-null
                                        else
                                                prev.next = next;
                                        e.recordRemoval(this);
                                        return e;
                                }
                                prev = e;
                                e = next;
                        }
    
                        return e;
                }
  5. 總結:

    1.7HashMap需要注意的是在擴容時,不是到達閾值就會擴容的,還要判斷當前位置是否有值,來決定會否擴容,還有就是擴容的時候是遍歷了每個位置的鏈表,重新計算hash和位置,然後插入新的table,每條鏈的順序是和原來相反的,這樣如果數據量很大,其實很消耗性能.還有就是採用鏈表的數據結構來存儲數據,如果hash碰撞嚴重的話,這條鏈就會很長,這樣不管是get,或者put都需要遍歷鏈,這樣也遍歷也很慢,這是1.7HashMap個人覺得一些缺陷吧(因爲看了1.8).
    PS 1.7的HashMap在多線程下擴容會導致環鏈,然後導致再次遍歷鏈表的時候回是死循環,進而cpu100%,所以多線程下就不要用HashMap.

Jdk 1.8

  1. 數據結構

    1.8的版本的HashMap採用數組+鏈表+紅黑樹的數據結構來存儲數據,還是通過hash & (tab.length - 1)來確定在數組的位置,不過在數據的存儲方面加了一個紅黑樹,當鏈表的大於等於8時,並且table的長度大於等於64時,就把這個鏈樹化,不然還是擴容.增加紅黑樹,是爲了提高查找節點的時間.結構如下圖所示.

    HashMap 源碼淺析(1.7和1.8)

  2. 基本成員變量
    capacity 容量

    /**
     * 初始容量
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    max_capacity 最大容量

    /**
     * 最大容量
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    loadFactor 負載因子

    /**
     * 負載因子
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    treeify_threshold 樹化(轉換爲紅黑樹)的閾值

    // 鏈表轉爲紅黑樹的閾值,第9個節點
    static final int TREEIFY_THRESHOLD = 8; 

    untreeify_threshold 轉換爲鏈表的閾值

    // 紅黑樹轉爲鏈表的閾值,6個節點轉移
    static final int UNTREEIFY_THRESHOLD = 6;

    min_treeify_capacity 樹化的最小容量

    // 轉紅黑樹時,table的最小長度
    static final int MIN_TREEIFY_CAPACITY = 64;

    node 鏈表

    static class Node<K,V> implements Map.Entry<K,V> {
        // 當前node的hash
        final int hash;
        final K key;
        V value;
        // 指向下個node
        Node<K,V> next;
    
        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

    TreeNode 紅黑樹

    static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;  // 父節點
        TreeNode<K,V> left;    // 左兒子節點
        TreeNode<K,V> right;   // 右兒子基點
        TreeNode<K,V> prev;    // 上一個節點
        boolean red;           // 是否爲紅色
        TreeNode(int hash, K key, V val, Node<K,V> next) {
            super(hash, key, val, next);
        }
  3. 構造方法
    有參構造 (和1.7一樣)
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                    initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                    loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

    無參構造(和1.7一樣)

    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
  4. 基本方法
    Put()方法
    執行流程:
    (1) 判斷當前table有沒有初始化,沒有就調用resize()方法初始化.(resize方法既是擴容也是初始化)
    (2) 判斷算出的位置i處有沒有值,沒有值,創建一個新的node,插入i位置.
    (3) 當前i位置有值,判斷頭結點的hash和key是否和傳入的key和hash相等,相等則記錄這個e.
    (4) 與頭節點的key和hash不同,判斷節點是否是樹節點,如果是,調用樹節點的插入方法putTreeVal()方法.(佔時不瞭解紅黑樹的底層方法實現邏輯,待續).
    (5) 不是樹結構,那證明是鏈表結構,遍歷鏈表結構,並記錄鏈表長度binCount.主要做了兩步,(1)在鏈表裏找到和傳入的key和hash相等的基點,並記錄,(2) 沒有找到,創建一個節點,插入鏈表的尾部,並判斷鏈表長度有沒有大於等於8,如果是就調treeifyBin方法決定是否需要樹化.
    (6) 判斷前面記錄的e節點是否爲空,不爲空證明找到了相同的基點,那就替換value,返回oldValue.
    (7) 整個插入流程已經結束,接下來要判斷是否需要擴容,如果(++size > threshold)滿足,那麼就調用擴容方法resize();

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    
    /**
     * put
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab;
        Node<K,V> p;
        int n, i;
        if ((tab = table) == null || (n = tab.length) == 0) // table爲null,table的length爲0,table還沒有初始化
            n = (tab = resize()).length; //調用擴容方法,初始化table
        if ((p = tab[i = (n - 1) & hash]) == null) //table{[i] 沒有值,直接插入
            tab[i] = newNode(hash, key, value, null);
        else { // 有值
            Node<K,V> e; K k;
            if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k)))) // 找到了相同的key
                e = p;
            else if (p instanceof TreeNode) // 判斷是否是樹結構
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); // 調用樹的插入
            else {
                // 遍歷鏈表,並記錄節點的長度
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) { // 找到最後一個節點了
                        p.next = newNode(hash, key, value, null); //設置當前節點爲爲p的next
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 如果節點長度大於等於了8,就調用treeifyBin方法
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k)))) // 找到了相同的key
                        // 此時break,返回的是e.key與傳入的key相等的e,以便下面進行替換
                        break;
                    p = e;
                }
            }
            // e != null 說明前面的遍歷找到了相同的key,下面就行替換,返回舊值
            if (e != null) { // existing mapping for key //
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null) // onlyIfAbsent默認是false
                    e.value = value; // 替換value
                afterNodeAccess(e);
                return oldValue; // 返回舊的value
            }
        }
        ++modCount;
        //  當前值大於閾值,進行擴容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

    resize()方法(初始化和擴容都是創建新的table)

    /**
     * 擴容
     * @return
     */
    final Node<K,V>[] resize() {
        // 舊table
        Node<K,V>[] oldTab = table;
        // 舊的table長度
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        // 舊的閾值
        int oldThr = threshold;
        int newCap, newThr = 0;
        //  證明就table,已經被初始化了
        if (oldCap > 0) {
            // 如果舊的table大於做大值,閾值就設置爲Internet的最大值,返回
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            // 給newCap賦值 newCap = oldCap << 1 (* 2^1) < MAXIMUM_CAPACITY(1 << 30)
            // oldCap >= 16 證明已經初始化過了,現在是擴容(假如oldCap就是16)
            // 新閾值 newThr = (oldThr = threshold) = 12 << 1(12 * 2^1)
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                    oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold // 新的閾值
        }
        // 這種情況是table還沒有初始化
        // oldThr >0 是因爲在有參構造裏面會把cap賦值給threshold
        else if (oldThr > 0) // initial capacity was placed in threshold
            // 容量就是oldThr
            newCap = oldThr;
        // 無參構造,容量和閾值都使用默認
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        // newThr=0證明使用的是有參構造,容量有值,閾值沒有值
        // 所以初始化閾值
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                    (int)ft : Integer.MAX_VALUE);
        }
    
        /** 上面屬於table參數準備部分,分爲初始化或者擴容 */
    
        // 新的閾值
        threshold = newThr;
        // 創建新的table
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            // 開始遍歷舊的table,進行數據遷移
            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
                        // 鏈表結構的數據 lo 表示位置不變 hi表示位置是原來的位置+oldCap
                        // 是通過(e.hash & oldCap) == 0 這句話來判斷的
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            // 解釋這句話話的意思,表示爲0,位置不變,不爲0時位置變化
                            // 等於0時,也就代表了,e.hash & (oldCap - 1) = e.hash (newCap - 1)
                            // 分析爲什麼會這樣
                            // e.hash & oldCap 解釋下這句話 假設oldCap是默認值 16
                            // 那麼16的二進制是 10000  那麼我們用當前的hash&16 只有兩種結果 0 或者 16
                            //  等於1 只有當前hash值的二進制的後5位的高位的第5位爲1,才能是16
                            // 等於0 ,相反高位5位後面是任意數字即可,也就是Cap -1 ,所以說爲0,就代表了e.hahs & (16 - 1)
                            // 所以,上面就得出結論,如果第五位爲0.那麼我們只去後面4爲做運算(16-1),所以說位置不變
                            // 如果第五位是1,那麼我們就需要後面5位去做運算(32-1),那麼如果第5位是1
                            // 現在的位置就是[j(原來的位置)+oldCap]
                            // 那麼我們這麼理解,先來分析下 e.hash (newCap - 1)
                            // 原來是16,那麼擴容後就是32,所以newCap - 1 = 31,二進制就是 1 1111
                            //  所以啊我們可以把這個二進制拆一下 10000 + 1111 = 16 + 15
                            //  原來的e.hash & (32 - 1) 就可以拆成 e.hash & 16 + e.hash & 15
                            //  所以結合上面的情況,運來的位置變與不變,其實取決於e.hash的高位第5位
                            //  是0,還是1
                            //  分析0,e.hash & 10000(16) + e.hahs & 1111(15)既然高位是0,那麼e.hash & 16 就是 0
                            //  e.hahs 的第五爲0 & 10000 所以結果是 0 ,那麼就取 e.hash & 15的位置了
                            //  分析是1,e.hahs的第五位是1,那麼就是 e.hash(高5位1) & 10000 + e.hash & 15,
                            //  由於高5位都是1,所以1&1 = 1,所以e.hash & 16 = 16,所以就是 16+e.hash & 15(原來的位置)
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null) // 第一次循環 tail 爲null,所以頭和尾都是 e
                                    loHead = e;  // 頭部是當前的e
                                else             // 接下來的循環tail不爲null
                                    loTail.next = e; // loTail 是上一次滿足if的e
                                                     // e 是這一次滿足if的e
                                                     // 所以loTail.next = e的目的就是,上一次滿足if的e指向下一次滿足if的e
                                                     // 代碼就是loTail.next = e
                                loTail = e;  // 每次循環尾部就是當前節點
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        // 位置不變還是j
                        if (loTail != null) {
                            loTail.next = null; // 尾部節點的next設置爲null
                            newTab[j] = loHead; // 設置頭結點指向table[j] (第一次循環時,head=tail,接下來循環給tail追加節點)
                        }
                        // 位置變化,是 j+oldCap
                        if (hiTail != null) {
                            hiTail.next = null; 
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章