前言
本文從三個部分去探究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的紅黑樹轉回連結串列的具體時機”~