HashMap在JDK1.8版本尾插法實現解析


寫在前面:
先解釋下何為尾插法。大家都知道HashMap在JDK1.8版本經過優化之后,整體的數據結構變成了數組+鏈表+紅黑樹這樣的形式。而尾插法說的就是在往HashMap里面put元素時,數組桶位上面還是未轉化為紅黑樹的鏈表,此時新增在鏈表上元素的位置為鏈表尾部,故名尾插法。

前面聊了HashMap在JDK1.7版本的頭插法實現,現在看看HashMap到了JDK1.8版本升級之后的變化。

先上代碼:

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

平時java代碼都是調的這個方法,實際實現是putVal(hash(key), key, value, false, true):

    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;
            // key的hash值經過位運算之后再和數組長度-1得到的值運算得到key在數組的下標
            // 若數組的這個位置還沒有元素則直接將key-value放進去
            if ((p = tab[i = (n - 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
            else {
                // 若該下標位置已有元素
                Node<K,V> e; K k;
                // 是否已有元素的key值與新增元素的key判斷是同一個
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    // 直接覆蓋value
                    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;
                    }
                }
                /**
                * 刪除部分代碼
                */
            }
            ++modCount;
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
            return null;
        }

接下來看 p.next = newNode(hash, key, value, null)這一句的具體方法實現,這也是HashMap在JDK1.8尾插法的實現了:

    Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
        return new Node<>(hash, key, value, next);
    }

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

在這個方法里面,構造了一個新節點Node(JDK1.8的新實現,繼承自Entry<K,V>),后繼節點指向null說明它在鏈表的位置后面是沒有元素的。

而在p.next = newNode(hash, key, value, null)這一句,是將新構造的Node節點指向原來遍歷鏈表查找到的最后一個元素的后繼節點。最終的效果其實就是將新元素追加到鏈表的尾部了,這也就是HashMap在JDK1.8的尾插法。

稍微提幾點:
1、在上一篇文章中提到鏈表遍歷查找元素是比較慢的,在HashMap中put元素發現數組桶位上已有元素,接着遍歷桶位上的鏈表查找是否有相同key的過程稱為hash碰撞,這是比較耗性能的。
而為了避免鏈表過長遍歷時間過大的問題,在JDK1.8采用了數據+鏈表+紅黑樹的結構。在往鏈表上新增元素時發現鏈表長度超過8時,會進入鏈表轉紅黑樹的方法,然后再判斷數組長度是否不小於64,若滿足條件則將鏈表轉化為紅黑樹。

2、至於JDK1.8的鏈表插入元素為什么改為了尾插法,則是為了避免出現逆序且鏈表死循環的問題(JDK1.7的HashMap擴容導致死循環了解哈)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM