Java原始碼閱讀筆記之TreeNode
序
-
寫下這篇筆記,是用於記錄我點點滴滴的成長,把自己會的東西記錄成筆記,一是做個記錄,二是再次用到的時候不用再百度別人的,都是自己看的東西,有不對的地方還望雅正
-
我在簡書建立了一個專題,叫原始碼閱讀筆記 ,目的是為了記錄自己看過的原始碼,希望能結交到和我一樣喜歡研究原始碼的你,我認為,沒有人是孤獨的,只是或許沒有找到自己的圈子,專題不用做大,能吸引幾個朋友一起經常交流一下我就心滿意足了。
-
HashMap使用Node[] 中儲存TreeNode的形式作為資料的儲存結構,故要掌握HashMap需要先看懂TreeNode
正文
TreeNode結構
static final class TreeNode<K,V>{ //Node hash//[(n-1) & hash]定位陣列的位置 key//key.hashCode()為hash提供原始hashCode value//值 next//與prev聯合使用實現鏈棧結構 //LinkedHashMap.Entry<K,V>,TreeNode內暫未涉及 before after //TreeNode parent//與left、right聯合使用實現樹結構 left right prev red//bool,記錄樹節點顏色 /** * 紅黑樹操作 * 包括:樹旋轉、插入/刪除節點後平衡紅黑樹 */ /** * 基本操作 * 包括:樹化、鏈棧化、增刪查節點、根節點變更 */ }
鏈棧:prev + next實現,節點數<7
樹:parent + left + right實現
TreeNode具備紅黑樹的性質,但又有異於紅黑樹,由於兩種儲存形式的存在,插入或刪除都要實現兩部分的邏輯以及進行當前儲存形式的判斷
方法目錄
- 樹旋轉(rotateLeft + rotateRight)
- 儲存模式轉換(treeify + untreeify)
- 根節點操作(root + moveRootToFront)
- 節點合理檢查(checkInvariants)
- 查詢節點(find)
- 插入節點(putTreeVal + balanceInsertion)
- 刪除節點(removeTreeNode + balanceDeletion)
- 節點拆分(split)
rotateLeft
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root, TreeNode<K,V> p) { TreeNode<K,V> r, pp, rl; if (p != null && (r = p.right) != null) { if ((rl = p.right = r.left) != null) rl.parent = p; if ((pp = r.parent = p.parent) == null) (root = r).red = false; else if (pp.left == p) pp.left = r; else pp.right = r; r.left = p; p.parent = r; } return root; }
方法實現,本質上就是把r換到p的位置,其他的節點根據大小放到相應的位置

rotateLeft
rotateRight
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root, TreeNode<K,V> p) { TreeNode<K,V> l, pp, lr; if (p != null && (l = p.left) != null) { if ((lr = p.left = l.right) != null) lr.parent = p; if ((pp = l.parent = p.parent) == null) (root = l).red = false; else if (pp.right == p) pp.right = l; else pp.left = l; l.right = p; p.parent = l; } return root; }
方法實現,本質上就是把L換到p的位置,其他的節點根據大小放到相應的位置

rotateRight
treeify
final void treeify(Node<K,V>[] tab) { TreeNode<K,V> root = null; for (TreeNode<K,V> x = this, next; x != null; x = next) { next = (TreeNode<K,V>)x.next; x.left = x.right = null; //當前節點為樹的根節點 if (root == null) { x.parent = null; x.red = false; root = x; } else { K k = x.key; int h = x.hash; Class<?> kc = null; for (TreeNode<K,V> p = root;;) { int dir, ph; K pk = p.key; //根據hash值判斷樹節點的位置 if ((ph = p.hash) > h) dir = -1; else if (ph < h) dir = 1; else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) dir = tieBreakOrder(k, pk); TreeNode<K,V> xp = p; //插入樹節點 if ((p = (dir <= 0) ? p.left : p.right) == null) { x.parent = xp; if (dir <= 0) xp.left = x; else xp.right = x; //設定節點顏色和平衡紅黑樹 root = balanceInsertion(root, x); break; } } } } //節點插入完畢後在陣列中儲存樹的根節點 moveRootToFront(tab, root); }
方法實現,鏈棧 => 樹
- 從鏈棧根據next依次取出清空left and right的節點
- 根據hash值建立樹節點,樹節點顏色由balanceInsertion實現(每次插入都預設為紅色節點再平衡插入)
untreeify
final Node<K,V> untreeify(HashMap<K,V> map) { Node<K,V> hd = null, tl = null; for (Node<K,V> q = this; q != null; q = q.next) { //replacementNode new一個Node,根據q的k,v,h,next Node<K,V> p = map.replacementNode(q, null); if (tl == null) hd = p; else tl.next = p; tl = p; } return hd; }
方法實現,根據當前節點的next關係提取節點,節點提供key,value,hash用於new Node節點建立鏈棧
root
final TreeNode<K,V> root() { for (TreeNode<K,V> r = this, p;;) { if ((p = r.parent) == null) return r; r = p; } }
方法實現,根據parent向上查詢根節點
moveRootToFront
static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) { int n; if (root != null && tab != null && (n = tab.length) > 0) { //陣列定位 int index = (n - 1) & root.hash; TreeNode<K,V> first = (TreeNode<K,V>)tab[index]; if (root != first) { Node<K,V> rn; tab[index] = root; //把root剝離出來 TreeNode<K,V> rp = root.prev; if ((rn = root.next) != null) ((TreeNode<K,V>)rn).prev = rp; if (rp != null) rp.next = rn; //root移至first的prev形成根節點 if (first != null) first.prev = root; root.next = first; root.prev = null; } assert checkInvariants(root); } }
方法實現,把操作節點從鏈棧中取出,移至根節點的prev形成新的根節點
checkInvariants
static <K,V> boolean checkInvariants(TreeNode<K,V> t) { //得到當前節點有關的所有節點 TreeNode<K,V> tp = t.parent, tl = t.left, tr = t.right, tb = t.prev, tn = (TreeNode<K,V>)t.next; //檢查鏈棧節點連結 if (tb != null && tb.next != t) return false; if (tn != null && tn.prev != t) return false; //檢查樹節點連結 if (tp != null && t != tp.left && t != tp.right) return false; if (tl != null && (tl.parent != t || tl.hash > t.hash)) return false; if (tr != null && (tr.parent != t || tr.hash < t.hash)) return false; //檢查當前節點顏色 if (t.red && tl != null && tl.red && tr != null && tr.red) return false; //遞歸向下檢查節點 if (tl != null && !checkInvariants(tl)) return false; if (tr != null && !checkInvariants(tr)) return false; return true; }
方法實現,檢查鏈棧 + 紅黑樹
- 鏈棧,prev + next,雙向判斷檢查
- 紅黑樹,parent + left and right,
1 雙向判斷檢查
2 紅黑樹節點顏色判斷 - 遞迴實現向下查詢
find
final TreeNode<K,V> find(int h, Object k, Class<?> kc) { TreeNode<K,V> p = this; do { int ph, dir; K pk; TreeNode<K,V> pl = p.left, pr = p.right, q; //對比hash值 if ((ph = p.hash) > h) p = pl; else if (ph < h) p = pr; //hash值相同時 //判斷是否為結果 else if ((pk = p.key) == k || (k != null && k.equals(pk))) return p; //hash值相同,一側節點為空則直接進入 else if (pl == null) p = pr; else if (pr == null) p = pl; //使用HashMap定義的方法判斷結果在哪側 else if ((kc != null || (kc = comparableClassFor(k)) != null) && (dir = compareComparables(kc, k, pk)) != 0) p = (dir < 0) ? pl : pr; //用盡一切手段無法判斷結果在哪側,則遞迴進入右邊查詢 else if ((q = pr.find(h, k, kc)) != null) return q; else p = pl; } while (p != null); //查詢無果 return null; }
方法實現,h表示使用HashMap規則的key的hash值,
- hash值不同
1 通過與查詢節點的hash值對比確定方向向下尋找 - hash值相同
1 判斷當前節點是否為查詢的key
2 一側為空直接向下查詢
3 使用HashMap定義的對比方法來判斷向下查詢的方向
4 遞迴進入右側尋找
5 只剩下左邊了
這是一個非常有意思的方法,它包含了TreeNode中的所有用於向下判斷方向的方法
putTreeVal
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab, int h, K k, V v) { Class<?> kc = null; boolean searched = false; TreeNode<K,V> root = (parent != null) ? root() : this; for (TreeNode<K,V> p = root;;) { int dir, ph; K pk; //通過hash和key判斷 //hash值不相同 if ((ph = p.hash) > h) dir = -1; else if (ph < h) dir = 1; //hash值相同 //key已存在,不能存在相同的key else if ((pk = p.key) == k || (k != null && k.equals(pk))) return p; //不同key的hash值相同,需要用到HashMap定義的判斷方法 else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) { if (!searched) { TreeNode<K,V> q, ch; searched = true; if (((ch = p.left) != null && (q = ch.find(h, k, kc)) != null) || ((ch = p.right) != null && (q = ch.find(h, k, kc)) != null)) return q; } dir = tieBreakOrder(k, pk); } //找到空節點,插入新節點 TreeNode<K,V> xp = p; if ((p = (dir <= 0) ? p.left : p.right) == null) { Node<K,V> xpn = xp.next; TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn); if (dir <= 0) xp.left = x; else xp.right = x; xp.next = x; x.parent = x.prev = xp; if (xpn != null) ((TreeNode<K,V>)xpn).prev = x; //新節點插入樹,平衡紅黑樹 moveRootToFront(tab, balanceInsertion(root, x)); return null; } } }
方法實現,判斷key若不存在(HashMap中不能存在相同的key),定位 + 插入
- 定位,根據hash值判斷,向下遍歷
1 hash值不同,根據大小判斷向下迴圈的方向
2 hash值相同,可能性不大,但是存在,HashMap事先預想到這種情況已經實現了判斷的方法,hash值相同則呼叫HashMap的方法實現判斷方向 - 插入,迴圈遍歷到空節點時可進行插入,
1 連結perv + next
2 連結parent + left and right
3 設定顏色和平衡紅黑樹 - 總結
1 可能的插入情況

.jpg
balanceInsertion
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, TreeNode<K,V> x) { //插入的節點預設為紅色 x.red = true; for (TreeNode<K,V> xp, xpp, xppl, xppr;;) { //return 根節點 if ((xp = x.parent) == null) { x.red = false; return x; } //return 根節點的子節點 else if (!xp.red || (xpp = xp.parent) == null) return root; //對稱操作,父節點位於左節點 if (xp == (xppl = xpp.left)) { //叔父節點為紅色,顏色變更實現樹平衡 if ((xppr = xpp.right) != null && xppr.red) { xppr.red = false; xp.red = false; xpp.red = true; x = xpp; } else { //旋轉父節點平衡插入節點為右節點的情況 if (x == xp.right) { root = rotateLeft(root, x = xp); xpp = (xp = x.parent) == null ? null : xp.parent; } //旋轉祖父節點平衡插入節點 if (xp != null) { xp.red = false; if (xpp != null) { xpp.red = true; root = rotateRight(root, xpp); } } } } //對稱操作,父節點位於右節點 else { if (xppl != null && xppl.red) { xppl.red = false; xp.red = false; xpp.red = true; x = xpp; } else { if (x == xp.left) { root = rotateRight(root, x = xp); xpp = (xp = x.parent) == null ? null : xp.parent; } if (xp != null) { xp.red = false; if (xpp != null) { xpp.red = true; root = rotateLeft(root, xpp); } } } } } }
方法實現,確定輸出情況 + 分別處理可能的插入情況
- 輸出情況,插入節點為根節點或父節點為黑色節點
- 處理可能的插入情況
1 叔父節點為紅色,簡單的顏色變更

2 插入節點為右節點需要旋轉至左節點,再旋轉父節點平衡樹

情況1
- 平衡樹有可能導致根節點發生改變,每次平衡樹後都應該在table陣列中重置新節點
- 最多兩次樹旋轉
1 插入節點為右節點
2 旋轉祖父節點平衡插入當前節點
removeTreeNode
/** * Removes the given node, that must be present before this call. * This is messier than typical red-black deletion code because we * cannot swap the contents of an interior node with a leaf * successor that is pinned by "next" pointers that are accessible * independently during traversal. So instead we swap the tree * linkages. If the current tree appears to have too few nodes, * the bin is converted back to a plain bin. (The test triggers * somewhere between 2 and 6 nodes, depending on tree structure). */ final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab, boolean movable) { // section 1:通過prev和next刪除當前節點 int n; if (tab == null || (n = tab.length) == 0) return; int index = (n - 1) & hash; TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl; TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev; if (pred == null) tab[index] = first = succ; else pred.next = succ; if (succ != null) succ.prev = pred; if (first == null) return; // section 2:當節點數量小於7時轉換成鏈棧的形式儲存 if (root.parent != null) root = root.root(); if (root == null || root.right == null || (rl = root.left) == null || rl.left == null) { tab[index] = first.untreeify(map);// too small return; } // section 3:判斷當前樹節點情況 TreeNode<K,V> p = this, pl = left, pr = right, replacement; if (pl != null && pr != null) { TreeNode<K,V> s = pr, sl; while ((sl = s.left) != null) // find successor s = sl; boolean c = s.red; s.red = p.red; p.red = c; // swap colors TreeNode<K,V> sr = s.right; TreeNode<K,V> pp = p.parent; if (s == pr) { // p was s's direct parent p.parent = s; s.right = p; } else { TreeNode<K,V> sp = s.parent; if ((p.parent = sp) != null) { if (s == sp.left) sp.left = p; else sp.right = p; } if ((s.right = pr) != null) pr.parent = s; } p.left = null; if ((p.right = sr) != null) sr.parent = p; if ((s.left = pl) != null) pl.parent = s; if ((s.parent = pp) == null) root = s; else if (p == pp.left) pp.left = s; else pp.right = s; if (sr != null) replacement = sr; else replacement = p; } else if (pl != null) replacement = pl; else if (pr != null) replacement = pr; else replacement = p; // section 4:實現刪除樹節點邏輯 if (replacement != p) { TreeNode<K,V> pp = replacement.parent = p.parent; if (pp == null) root = replacement; else if (p == pp.left) pp.left = replacement; else pp.right = replacement; p.left = p.right = p.parent = null; } TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement); if (replacement == p) {// detach TreeNode<K,V> pp = p.parent; p.parent = null; if (pp != null) { if (p == pp.left) pp.left = null; else if (p == pp.right) pp.right = null; } } if (movable) moveRootToFront(tab, r); }
方法實現,鏈棧 + 樹實現刪除當前節點
鏈棧:prev、next
樹:parent、left、right
具體步驟為,
- 先通過prev和next實現刪除邏輯
- 由節點數判斷當前儲存形式
- 若為樹則追加實現parent、left、right
- 由根節點的left.left節點作為判斷當前儲存狀態的核心,鏈棧的節點數最多為6個

判斷資料儲存形式
-
刪除樹節點的方法,刪除時使其滿足節點位於單鏈上(操作簡便)
樹節點刪除
balanceDeletion
static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root, TreeNode<K,V> x) { for (TreeNode<K,V> xp, xpl, xpr;;){ //結束迴圈條件1,平衡節點 == root if (x == null || x == root) return root; else if ((xp = x.parent) == null) { x.red = false; return x; } //迴圈結束條件2,平衡節點為紅色 else if (x.red) { x.red = false; return root; } //向上平衡 //平衡節點位於左節點 else if ((xpl = xp.left) == x) { if ((xpr = xp.right) != null && xpr.red) { xpr.red = false; xp.red = true; root = rotateLeft(root, xp); xpr = (xp = x.parent) == null ? null : xp.right; } if (xpr == null) x = xp; else { TreeNode<K,V> sl = xpr.left, sr = xpr.right; if ((sr == null || !sr.red) && (sl == null || !sl.red)) { xpr.red = true; x = xp; } else { if (sr == null || !sr.red) { if (sl != null) sl.red = false; xpr.red = true; root = rotateRight(root, xpr); xpr = (xp = x.parent) == null ? null : xp.right; } if (xpr != null) { xpr.red = (xp == null) ? false : xp.red; if ((sr = xpr.right) != null) sr.red = false; } if (xp != null) { xp.red = false; root = rotateLeft(root, xp); } x = root; } } } //平衡節點位於右節點 else { // symmetric if (xpl != null && xpl.red) { xpl.red = false; xp.red = true; root = rotateRight(root, xp); xpl = (xp = x.parent) == null ? null : xp.left; } if (xpl == null) x = xp; else { TreeNode<K,V> sl = xpl.left, sr = xpl.right; if ((sl == null || !sl.red) && (sr == null || !sr.red)) { xpl.red = true; x = xp; } else { if (sl == null || !sl.red) { if (sr != null) sr.red = false; xpl.red = true; root = rotateLeft(root, xpl); xpl = (xp = x.parent) == null ? null : xp.left; } if (xpl != null) { xpl.red = (xp == null) ? false : xp.red; if ((sl = xpl.left) != null) sl.red = false; } if (xp != null) { xp.red = false; root = rotateRight(root, xp); } x = root; } } } } }
方法實現,由結束判斷 + 向上平衡兩部分實現
-
結束判斷,平衡節點為root或red,顏色變更結束平衡
-
向上平衡(平衡節點 = xp),迴圈實現從刪除節點開始向父節點逐步平衡,每次平衡需要判斷兄弟節點和兄弟節點的內側子節點的顏色,紅色則需要通過樹旋轉來實現平衡
-
平衡樹有可能導致根節點發生改變,每次平衡樹後都應該在table陣列中重置新節點
split
/** * Splits nodes in a tree bin into lower and upper tree bins, * or untreeifies if now too small. Called only from resize; * see above discussion about split bits and indices. * * @param map the map * @param tab the table for recording bin heads * @param index the index of the table being split * @param bit the bit of hash to split on */ final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) { TreeNode<K,V> b = this; // Relink into lo and hi lists, preserving order TreeNode<K,V> loHead = null, loTail = null; TreeNode<K,V> hiHead = null, hiTail = null; int lc = 0, hc = 0; for (TreeNode<K,V> e = b, next; e != null; e = next) { //剝離節點 next = (TreeNode<K,V>)e.next; e.next = null; //連結可保留在原陣列位置的節點 if ((e.hash & bit) == 0) { if ((e.prev = loTail) == null) loHead = e; else loTail.next = e; loTail = e; ++lc; } //提取連結重新定義陣列位置的節點(陣列擴容後的新位置) else { if ((e.prev = hiTail) == null) hiHead = e; else hiTail.next = e; hiTail = e; ++hc; } } //儲存留在陣列原位置的節點,根據節點數判斷儲存型別 if (loHead != null) { if (lc <= UNTREEIFY_THRESHOLD) tab[index] = loHead.untreeify(map); else { tab[index] = loHead; if (hiHead != null) // (else is already treeified) loHead.treeify(tab); } } //儲存提取到陣列擴容新位置的節點,根據節點數判斷儲存型別 if (hiHead != null) { if (hc <= UNTREEIFY_THRESHOLD) tab[index + bit] = hiHead.untreeify(map); else { tab[index + bit] = hiHead; if (loHead != null) hiHead.treeify(tab); } } } //補充: /** * The bin count threshold for untreeifying a (split) bin during a * resize operation. Should be less than TREEIFY_THRESHOLD, and at * most 6 to mesh with shrinkage detection under removal. */ static final int UNTREEIFY_THRESHOLD = 6;
方法實現,節點拆分 + 判斷連結 + 重新儲存
- 節點拆分,通過next=null
- 判斷連結,
1 通過bit&hash區分 ==0(lo) 和 !=(hi),根據註釋介紹,該方法只有resize呼叫,進入該方法發現bit是原陣列的長度
2 建立lo和hi兩組節點來分別記錄兩種情況,如lo,loH--頭節點,loT--尾節點,lc,記錄節點數 - 根據頭節點非空和節點數判斷儲存形式存入陣列相應位置
個人
-
TreeNode中我收穫最大的一個方法,主要體現在bit&hash的判斷上,檢視resize可知,bit表示原陣列長度,即 原陣列長度&hash
1 比如原陣列為32,resize後為64,則用當前節點的hash&32,
即 當前節點的二進位制後六位&10 0000,
2 根據&的計算方式,實際上就是判斷當前節點的二進位制後六位中的第一位是否為1,第一位為1表示後六位>=10 0000(32),應該放到table[index + bit],即table[31]
3 總結,resize用迴圈拆分原陣列的每個位置,如32=>64,則實現table[1]中的節點留在 table[1] 還是 table[31] 。
後記
-
在TreeNode的原始碼中,我學會了,
1 紅黑樹,紅黑樹的性質和操作都懂了,就是有些時候可能的情況分析或者為什麼要這樣操作這點上還有欠缺,學無止境,我會持續儲存關注
2 對於紅黑樹,TreeNode的紅黑樹和通常的紅黑樹是有區別的,沒能完全掌握區別,比如,TreeNode沒有提到的NIL葉子節點等
3 對於hash值相同的情況,左右節點的判斷中,使用HashMap定義的判斷方法都是一筆帶過,後續看完HashMap後會補充
4 splite方法中的拆分判斷條件 bit & hash ,驚歎之餘,受益匪淺