jdk1.8原始碼解析:HashMap底層資料結構之連結串列轉紅黑樹的具體時機

賴皮梅發表於2019-08-01

前言

  本文從三個部分去探究HashMap的連結串列轉紅黑樹的具體時機:

    一、從HashMap中有關“連結串列轉紅黑樹”閾值的宣告;

    二、【重點】解析HashMap.put(K key, V value)的原始碼;

    三、測試;

 

一、從HashMap中有關“連結串列轉紅黑樹”閾值的宣告,簡單瞭解HashMap的連結串列轉紅黑樹的時機

  在 jdk1.8 HashMap底層資料結構:雜湊表+連結串列+紅黑樹(圖解+原始碼)的 “四、問題探究”中,我有稍微提到過雜湊表後面跟什麼資料結構是怎麼確定的:

  HashMap中有關“連結串列轉紅黑樹”閾值的宣告:

    /**
     * 使用紅黑樹(而不是連結串列)來存放元素。當向至少具有這麼多節點的連結串列再新增元素時,連結串列就將轉換為紅黑樹。
     * 該值必須大於2,並且應該至少為8,以便於刪除紅黑樹時轉回連結串列。
     */
    static final int TREEIFY_THRESHOLD = 8;

 

二、【重點】解析HashMap.put(K key, V value)的原始碼,去弄清楚連結串列轉紅黑樹的具體時機

  通過檢視HashMap的原始碼可以發現,它的put(K key, V value)方法呼叫了putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)來實現元素的新增。所以我們實際要看的是putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)的原始碼。

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); //直接放在雜湊表上的節點,並沒有特意標識其為頭節點,其實它就是"連結串列/紅黑樹.index(0)"
        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節點,找到該桶上的最後一個節點:
                        p.next = newNode(hash, key, value, null); //直接生成新節點,鏈在最後一個節點的後面;
//“binCount >= 7”:p從連結串列.index(0)開始,當binCount == 7時,p.index == 7,newNode.index == 8; //也就是說,當連結串列已經有8個節點了,此時再新鏈上第9個節點,在成功新增了這個新節點之後,立馬做連結串列轉紅黑樹。 if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash); //連結串列轉紅黑樹 break; } …… p = e; } } …… } } …… }

  通過原始碼解析,我們已經很清楚HashMap是在“當連結串列已經有8個節點了,此時再新鏈上第9個節點,在成功新增了這個新節點之後,立馬做連結串列轉紅黑樹”。

 

三、通過測試,進一步理解連結串列轉紅黑樹的具體時機

  1. 先建立一個Node類,它是HashMap的節點的一個簡化:

class Node {
    int info;
    Node next;
    public Node(int info) {
        this.info = info;
    }
}

  2. 簡化HashMap的put()方法,並進行測試:

public class A03Method_TreeifyBin {
    public static void main(String[] args) {
        A03Method_TreeifyBin treeifyBin = new A03Method_TreeifyBin();
        
        //假設下面建立的所有Node都是要放到雜湊表上的同一個桶上的(即通過計算它們的雜湊值定位到了同一個桶):
        Node firstNode = new Node(0);//firstNode作為直接放在桶上的節點。
        for(int i = 1; i < 10; i++) {//已有firstNode,再put進9個節點
            treeifyBin.put(firstNode, new Node(i));//new出的新節點依次鏈在firstNode後面,形成一個連結串列。
        }
    }
    
    /**
     * 該方法擷取自HashMap.putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)方法中有關“連結串列轉紅黑樹的時機”那部分程式碼。 
     * 該方法是對HashMap.putVal(……)原始碼做簡化後的方法,可以參照原始碼來看該方法。
     * 
     * @param p       : 當前節點
     * @param newNode : 要新添進去的節點
     */
    public void put(Node p, Node newNode) {
        Node e;
        for (int binCount = 0;; ++binCount) {
            if ((e = p.next) == null) { 
                p.next = newNode; 
                if(binCount >= 7) {
                    //HashMap.putVal(……)原始碼中這裡執行了treeifyBin(tab, hash)方法,將連結串列轉為了紅黑樹。
                    System.out.println("binCount == " + binCount);
                    System.out.println("p.next.info : " + p.next.info);
                    System.out.println("p.info : " + p.info);
                    System.out.println("----------------------------");
                }
                break;
            }
            p = e;
        }
    }
}

  最後的輸出結果是:

    binCount == 7
    p.next.info : 8
    p.info : 7
    ----------------------------
    binCount == 8
    p.next.info : 9
    p.info : 8
    ----------------------------

 

  從輸出結果我們可以得知:

    從firstNode後面鏈上new Node(1)開始,從輸出結果可以看到,一旦 binCount == 7 就做連結串列轉紅黑樹的操作;

    此時"p.next = newNode;"的p是new Node(7),newNode是new Node(8);

    也就是說:當連結串列已經有8個元素了(firstNode到new Node(7)),此時put進第9個元素(new Node(8)),先完成第9個元素的put,然後立刻做連結串列轉紅黑樹。這個結論和第2點中得到的結論一致。

  

  到這裡,有關“HashMap的連結串列轉紅黑樹的具體時機”算是表述完了,有時間我們再來探究“HashMap的紅黑樹轉回連結串列的具體時機”~

 

相關文章