HashMap源碼之put()方法

/**
 * 經常使用方法put
 */
public V put(K key, V value) {
    // 獲取key的hash並命令putval方法替換現有值與非建立模式
    return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    // tab - 當前hash桶的引用
    // p - key所表明的節點(此節點不必定是目標節點,而僅僅是hash與桶長度的計算值相同而已)(它不爲空時多是鏈表或紅黑樹)
    // n - 當前桶的容量
    // i - key在桶中的下標(同p,不表明目標節點)
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 初始化局部變量tab並判斷是否爲空,初始化局部變量n並判斷是否爲0
    // PS: 源碼中大量的使用了這種書寫方法,不知道放在某寫大廠裏會怎麼樣(斜眼笑)
    if ((tab = table) == null || (n = tab.length) == 0)
        // 當tab爲空或n爲0時,代表hash桶還沒有初始化,調用resize()方法,進行初始化並再次初始化局部變量tab與n
        n = (tab = resize()).length;
    
    // 初始化p與i
    // 這裏使用了(n - 1) & hash的方式計算key在桶中的下標.這個在後面單獨說明
    // 當p是否爲空
    if ((p = tab[i = (n - 1) & hash]) == null)
        // p爲空,調用newNode方法初始化節點並賦值到tab對應下標
        tab[i] = newNode(hash, key, value, null);
    else {
        // p不爲空,發生碰撞.進行後續處理
        
        // e - 目標節點
        // k - 目標節點的key
        Node<K,V> e; K k;
        
        // 判斷key是否相同.(這裏除了比較key之外,還比較了hash)
        // 注意,這裏同時初始化了局部變量k,可是在第二組條件不知足的狀況下,沒有使用價值,能夠被忽略
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            // key相同,將e(目標節點)設置爲p
            e = p;
        // 判斷節點是不是紅黑樹
        else if (p instanceof TreeNode)
            // 肯定時,直接委派處理
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            // 走到這裏,表明當前節點爲普通鏈表,進行遍歷查找
            // 變量binCount只做爲是否達到tree化的閾值判斷條件.
            for (int binCount = 0; ; ++binCount) {
                
                // 獲取鏈表的下一個元素,並賦值到e(此時e是一箇中間變量,不肯定是不是目標節點)
                // 第一次for循環時,p表明hash桶中的節點(同時也是鏈表的頭部節點),以後一直等於p.next
                if ((e = p.next) == null) {
                    // 鏈表遍歷到末尾
                    
                    // 向鏈表中追加新的節點
                    p.next = newNode(hash, key, value, null);
                    
                    // 判斷當前鏈表長度是否達到tree閾值
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        // 調用treeifyBin方法直接處理
                        treeifyBin(tab, hash);
                    // 中斷循環
                    // 注意,此時局部變量e=null
                    break;
                }
                
                // 能走到此處,說明鏈表未結束,比較e的k是否相同(hash與==)
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    // key相同
                    break;
                
                // e既不爲null也不是目標節點,賦值到p,住被進行下次循環
                p = e;
            }
        }
        
        // 判斷e是否存在
        if (e != null) { // existing mapping for key
            // e不等於null說明操做爲"替換"
            
            // 緩存老值
            V oldValue = e.value;
            // 判斷是否必須替換或老值爲null
            if (!onlyIfAbsent || oldValue == null)
                // 必須替換或老值爲空,更新節點e的value
                e.value = value;
            // 調用回調
            afterNodeAccess(e);
            // 返回老值
            // 注意,這裏直接返回了,而沒有進行modCount更新與下面的後續操做
            return oldValue;
        }
    }
    
    // 除了更新鏈表節點之外,都會走到這裏(putTreeVal的返回值是什麼有待確認)
    // modCount+1
    ++modCount;
    // size+1(元素數量+1)
    // 判斷是否超過閾值
    if (++size > threshold)
        // 重置大小
        resize();
    // 調用後置節點插入回調
    afterNodeInsertion(evict);
    return null;
}
相關文章
相關標籤/搜索