HashMap的put方法源碼解析及其中兩種會觸發擴容的場景(足夠詳盡,有問題歡迎指正~)

分析HashMap的put方法的源碼後發現,HashMap的擴容方法在兩個場景下會被調用:

  1. 初始化HashMap的鏈表數組時,會被調用,用來初始化鏈表數組的初始容量爲16,以及初始化鏈表數組的閾值爲初始容量16*負載因子0.75=12;
  2. 當put到HashMap存儲的元素個數超過閾值時,會被調用,用來將鏈表數組的容量和閾值都擴大爲原來的2倍。
    具體,詳見下述的源碼解析:
    /*HashMap的put方法,實際上調用的是putVal方法/
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
}
/**
     * Implements Map.put and related methods.
     *
     * @param hash hash for key //這裏是已經用key的原始hashCode的高16位和低16位異或運算過的hashCode
     * @param key the key //key值 用於計算出要放的數組的索引位置
     * @param value the value to put //value值 數組索引位置上要放置的值
     * @param onlyIfAbsent if true, don't change existing value //false 若爲true,則不替換已存在的舊值,但HashMap調用此方法時給的爲false,所以會拿要放置的新值替換已存在的舊值
     * @param evict(驅逐) if false, the table is in creation mode. //true 表明數組已構造完畢,可以往裏面put了,false則表示數組還在初始化構造中。但此參數在HashMap中並沒有實際意義,實際被調用的方法是個空方法:void afterNodeInsertion(boolean evict) { }
     * @return previous value, or null if none//要放的位置上已有舊值則最後返回該舊值,否則返回null
     */
    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)//Node<K,V>[] table,用於裝載鏈表的數組爲空,說明數組尚未開始初始化
            n = (tab = resize()).length;//調用擴容方法爲數組初始化,初始化完後代碼繼續往下執行【使用默認容量值和閾值爲其初始化,newCap = DEFAULT_INITIAL_CAPACITY (16,must be a power of two);newThr = (int)(DEFAULT_LOAD_FACTOR(0.75) * DEFAULT_INITIAL_CAPACITY)】;
        if ((p = tab[i = (n - 1) & hash]) == null) //判斷根據key計算出的要放置的數組的索引位置上是否沒值,若沒值說明該數組索引位置的鏈表的頭節點還沒創建
            tab[i] = newNode(hash, key, value, null);//鏈表頭節點不存在,則調用 Node<>(hash, key, value, next)創建頭節點,並初始化頭節點的hash,key,value,next=null。 
        else {//索引位置已有值,說明鏈表頭節點已存在,則開始具體put操作
            Node<K,V> e; K k;
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))//要put的key對應的hash值及key值與鏈表頭節點裏的key對應的hash值和key值都相等,則說明該put操作是想要替換頭節點,
                e = p;//所以將頭節點的引用賦給Node<K,V> e保存下來,以便後續拿來對其繼續處理(用要put的新value替換掉舊value,後面可以看到替換值的相關處理)
            else if (p instanceof TreeNode) //若原節點類型爲紅黑樹結構的節點,則調用向樹中put的方法TreeNode.putTreeVal
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {//若非想要替換頭節點,且目前該索引位置(桶)的結構類型非紅黑樹結構,則put到鏈表的最後一個節點(即next爲空的鏈表的末尾節點)
                for (int binCount = 0; ; ++binCount) {binCount用來統計該索引位置上的鏈表()上元素個數,注意是從0開始第一個計數
                    if ((e = p.next) == null) {// 若next(即下一節點)爲空,即指鏈表的末尾節點
                      p.next = newNode(hash, key, value, null);//爲要put的鍵值對創建新節點放在原末尾j節點的next節點,成爲新的末尾節點
                      if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 因binCount是從0開始的,所以當binCount數值爲7的時候,統計到的鏈表元素個數已經達到8個,而當鏈表長度達到8時,會調用treefyBin方法轉成紅黑樹結構,以便提供更高的增刪查改性能
                        treeifyBin(tab, hash); 鏈表轉紅黑樹
                      break;//已經找到要操作的節點位置,所以不用再循環,故跳出
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))//走到此處說明,要put的鍵值對既非要替換頭節點,也非要在put到紅黑樹上,也不是要追加到鏈表末尾節點,而是要替換鏈表頭尾之間的某個節點(因此代碼走到此處,就說明要put的key的hash值和key值與鏈表頭尾之間的某個節點的key的hash值和key值都相等)
                        break; //已經找到要操作的節點位置,所以不用再循環,故跳出
                    p = e; //走到這裏說明截至目前尚未找到要操作的節點位置,繼續下一輪循環(此時的e已經被賦值爲了p.next,而此處又將p.next賦給了p,所以下次循環到p.next時,其實是向前遞進了一個節點)
                }
            }
            if (e != null) { // existing mapping for key  //此處是爲了處理上面處理邏輯中餘下的替換值的操作,即找到了要處理的節點位置,且在該位置已經有元素存在,需要把該位置上的元素的舊值替換成新值,並返回舊值
                V oldValue = e.value; //記錄下舊值,以便return
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value; //用新值替換舊值
                afterNodeAccess(e);//Callbacks to allow LinekdHashMap post-actions.供LinkedHashMap後續操作的回調方法(鉤子方法),於HashMap而言此方法無意義.
                return oldValue;
            }
        }
        ++modCount; //每次put都會加1(新增而非替換,替換的話在返回舊值處代碼就返回了),用於記錄變更次數
        if (++size > threshold) //每put一次size都會加1,當size超過此時的容量閾值時,也會發生擴容操作
            resize();//擴容操作
        afterNodeInsertion(evict);//同afterNodeAccess(e),也是供LinkedHashMap後續操作的回調方法,於HashMap而言此方法無實際意義
        return null; //新增操作無舊值可返回
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章