JDK1.8 HashMap源碼解讀 一一put方法

建議用 IDEA打開源碼配合來看

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

實際調用putVal方法:

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)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        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))))
            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);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

代碼有點長, 我們一點一點來看:

首先,定義了4個變量 (Node<K,V>[] tab; Node<K,V> p; int n, i;),先不管他是什么意思,然后把一個 HashMap 的一個成員變量賦值給 tab,

由于我們第一次調用put方法, 所以成員變量此時的值為null,為null會調用一個 resize()方法,我們來看一下這個方法做了些什么。

我們調用不帶參數的構造方法的時候,只對HashMap的負載因子進行了初始化,所以在這里的 threshold 的值為零,然后代碼走到了 else 這里,將默認的初始化大小(16)賦值給newCap,然后計算出Map的容量達到多少之后需要擴容。


這里將擴容的閥值賦值給成員變量 threshold 方便之后存取,然后new一個剛剛計算出數量的Note數組,再將Note數組賦值給成員變量table,此table就是HashMap具體存儲數據的地方,接下來,oldTab依然為null,所以直接返回初始化好的Note數組-->>

這里將初始化好的數組的大小賦值給變量n,然后通過 算法(數組的長度 - 1 & key的hash值) 計算出數組的某個下標位置存儲的元素賦值給 變量p,再判斷這個元素是否為null,如果是null的話,創建一個Note,把note數據存儲在計算好的Note數組的位置。

創建Note節點

note中存儲了key的hash值,鍵值對,以及對下一個note節點的引用,由此可見,這是一個單向鏈表的結構

我們put數據的時候,會有兩種情況,一是上面這種,數組下標節點沒有數據,第二種是有值,接下來我們看第二種情況

這里又會有三種情況:

  1. 拿當前Map中的Note節點的hash值與要put進去的key的hash判斷,相等的話,再比較Map中Note節點的key與傳進來的key地址是否一致,如果一致,則將值覆蓋,不一致就再繼續進行key的equals比較,為true就覆蓋,不然繼續判斷。
  2. 判斷當前節點是否是一個紅黑樹結構,如果是的話,將數據存儲到樹節點中。
  3. 如果都不滿足,則將數據添加到鏈表中。說的很簡單,我們繼續看一下代碼:

    p = 當前數組位置的元素
    e = 當前數組位置的元素的鏈表的下一個

先判斷當前節點鏈表是否有下一個,如果沒有,則初始化Note節點,加入到鏈表中,再判斷鏈表的長度,有沒有達到轉換樹結構的閥值,達到了則將鏈表轉換為樹結構。
如果當前節點鏈表有下一個,判斷節點數據的hash值與傳進來的hash是否相等,相等則判斷節點的key與傳進來的key的地址值比較和equals比較,滿足任何一條,就退出。不滿足就繼續迭代當前數組位置的鏈表,執行前面的邏輯。


最后把Map的Size加1, 判斷是否需要擴容。

resize() 方法中還有許多邏輯判斷,喜歡研究的自己去看吧,作者太菜,不愛看算法

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。