HashMap中的putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)解讀

在面試中我們會經常遇到關於HashMap的問題,這裏我寫了我對HashMap裏面一個挺重要的方法 putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)的理解,下面就是我對這個方法的理解。

其實putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)方法我們是沒有辦法直接調用的,其實在我們使用put(K key,V val)時,它裏面的內部就調用了putVal方法。

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

先講講putVal方法裏面的幾個參數的作用

 /**
     *
     * @param hash  由key計算出來的 hash值
     * @param key   要存儲的key
     * @param value  要存儲的value
     * @param onlyIfAbsent  如果當前位置已存在一個值,是否替換,false是替換,true是不替換
     * @param evict  表是否在創建模式,如果爲false,則表是在創建模式。
     */

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict)

下面的一張圖簡單解釋了putVal方法的作用

在這裏插入圖片描述

我將這個過程分爲了7步,分別是:

1、檢查table是否爲空,如果爲空就初始化

2.檢查table中位置爲(n -1 ) & hash 是否爲空,如果爲空,直接放入(這是放在數組裏)

3.如果桶中存在的元素的key和hash都相等,直接覆蓋舊value

4.判斷存放該元素的鏈表是否轉爲紅黑樹,如果爲紅黑樹,直接插入,此時上面3是不成立,hash值不相等,也就是key值不等(hash值是由key算出來的)

5.第3和第4都不成立,將插入元素存放在鏈表中(也有可能是紅黑樹)

6.存在key值和hash值相等的,直接覆蓋舊value

7.將記錄修改次數加1,判斷是否需要擴容,如果需要就擴容

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
       Node<K, V>[] tab;
       Node<K, V> p;
        int n, i;
        //1、檢查table是否爲空,如果爲空就初始化
        if ((tab = table) == null || (n = tab.length) == 0)
        //  初始化,也是擴容
            n = (tab = resize()).length;
         //2.檢查table中位置爲(n -1 ) & hash 是否爲空,如果爲空,直接放入(這是放在數組裏)
        if ((p = tab[i = (n - 1) & hash]) == null)
        //放入
            tab[i] = newNode(hash, key, value, null);
            //桶中已經存在元素了,也就是說 table中( n -1) & hash這個位置不爲空(發生碰撞了)
        else {
            Node<K, V> e;
            K k;
            //3.如果桶中存在的元素的key和hash都相等,直接覆蓋舊value
            if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
                //4.判斷存放該元素的鏈表是否轉爲紅黑樹,如果爲紅黑樹,直接插入,此時上面3是不成立,hash值不相等,也就是key值不等(hash值是由key算出來的)
            else if (p instanceof TreeNode)
            //存放在紅黑樹裏
                e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
                //5.第3和第4都不成立,將插入元素存放在鏈表中
            else {
            //循環鏈表,找到鏈表末尾插入元素
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //判斷當前鏈表的元素是否超過要轉換爲紅黑樹的閾值 (節點數超過8就要轉換爲紅黑樹)
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                           //轉換爲紅黑樹存儲
                            treeifyBin(tab, hash);
                        break;
                    }
                    //遍歷鏈表,看鏈表中是否存在hash和key與要插入進來的元素相同,如果相同,跳出循環。
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            //跳出循環
                        break;
                     // 跟 e = p.next 組成用來遍歷鏈表。
                    p = e;
                }
            }
            //6.存在key值和hash值相等的,直接覆蓋舊value
            if (e != null) { // existing mapping for key
            //取出e的value
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                //覆蓋
                    e.value = value;
                    //訪問後回調
                afterNodeAccess(e);
                return oldValue;
            }
        }
        //7.將記錄修改次數加1,判斷是否需要擴容,如果需要就擴容
        ++modCount;
        if (++size > threshold)
            resize();
            //插入後回調
        afterNodeInsertion(evict);
        return null;
    }

這裏藉助一張圖片更容易理解

在這裏插入圖片描述
圖片來自這裏,覺得圖片更容易看懂這個方法工作的流程。

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