在看HashMap的源碼時候看到了TreeNode。因此需要對其進行一個了解。是一個紅黑樹。可以百度一下紅黑樹的數據結構。分析了下源碼,還是比較枯燥的
紅黑樹的性質:本身是一個二叉查找樹(所有左節點的值都比右節點的小)。另:
- 節點是紅色或者黑色
- 根節點是黑色
- 每個葉節點(Nil節點,空節點)是黑色的
- 每個紅節點對應的兩個子節點都是黑色的(不可能有兩個相連的紅節點)。
- 從任意節點出發,到每個葉子節點都有相同的黑色節點。
這保證了紅黑數是平衡的,從根到葉子的最長的可能路徑不多於最短的可能路徑的兩倍長,因此插入、刪除查找的最壞情況是有所保證的,與樹的高度成正比。原因有兩點:
- 最短的可能路徑都是黑色節點。
- 最長的路徑是紅黑節點交替的路徑。
因為從同一節點出發,到每一個葉子有相同的黑色節點,所以保證了最長路徑是最短路徑的兩倍長。
一、成員變量與構造函數
1 static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { 2 //父節點 3 HashMap.TreeNode<K,V> parent; // red-black tree links 4 //左節點 5 HashMap.TreeNode<K,V> left; 6 //右節點 7 HashMap.TreeNode<K,V> right; 8 //用來刪除下一個節點用的,因此prev也就是上一個節點 9 HashMap.TreeNode<K,V> prev; // needed to unlink next upon deletion 10 //節點是否為紅色 11 boolean red; 12 TreeNode(int hash, K key, V val, HashMap.Node<K,V> next) { 13 super(hash, key, val, next); 14 } 15 }
二、Function root
1 /** 2 * 返回包含此節點的樹的根節點 3 */ 4 final HashMap.TreeNode<K,V> root() { 5 //定義兩個TreeNode,一個是父節點指針,指向當前節點的parent,所以功能很明顯了。就是遍歷直到頭節點 6 for (HashMap.TreeNode<K,V> r = this, p;;) { 7 if ((p = r.parent) == null) 8 return r; 9 r = p; 10 } 11 }
三、Function checkInvariants
1 /** 2 * 不變性檢查,保證紅黑樹的結構不改變。 3 */ 4 static <K, V> boolean checkInvariants(HashMap.TreeNode<K, V> t) { 5 HashMap.TreeNode<K, V> tp = t.parent, tl = t.left, tr = t.right, 6 tb = t.prev, tn = (HashMap.TreeNode<K, V>) t.next; 7 if (tb != null && tb.next != t) 8 return false; 9 if (tn != null && tn.prev != t) 10 return false; 11 if (tp != null && t != tp.left && t != tp.right) 12 return false; 13 if (tl != null && (tl.parent != t || tl.hash > t.hash)) 14 return false; 15 if (tr != null && (tr.parent != t || tr.hash < t.hash)) 16 return false; 17 if (t.red && tl != null && tl.red && tr != null && tr.red) 18 return false; 19 if (tl != null && !checkInvariants(tl)) 20 return false; 21 if (tr != null && !checkInvariants(tr)) 22 return false; 23 return true; 24 } 25 }
四、Function moveRootToFront
1 /** 2 * 確保所給的root是第一個節點。也就是把所給的root移到第一個節點。確保桶的紅黑樹的跟節點是root 3 */ 4 static <K, V> void moveRootToFront(HashMap.Node<K, V>[] tab, HashMap.TreeNode<K, V> root) { 5 int n; 6 if (root != null && tab != null && (n = tab.length) > 0) { 7 //根節點的位置 8 int index = (n - 1) & root.hash; 9 10 //鏈表的操作,把root移到第一個節點。root的next指向原先的頭節點,原先的頭節點的prev指向root; 11 //root的next的prev指向root的prev,root的prev指向root的next,即把root在prev和next中去掉 12 HashMap.TreeNode<K, V> first = (HashMap.TreeNode<K, V>) tab[index]; 13 if (root != first) { 14 HashMap.Node<K, V> rn; 15 tab[index] = root; 16 HashMap.TreeNode<K, V> rp = root.prev; 17 if ((rn = root.next) != null) 18 ((HashMap.TreeNode<K, V>) rn).prev = rp; 19 if (rp != null) 20 rp.next = rn; 21 if (first != null) 22 first.prev = root; 23 root.next = first; 24 root.prev = null; 25 } 26 //紅黑樹的一致性檢查 27 assert checkInvariants(root); 28 } 29 }
五、Function find
1 /** 2 * Finds the node starting at root p with the given hash and key. 3 * The kc argument caches comparableClassFor(key) upon first use 4 * comparing keys. 5 */ 6 final HashMap.TreeNode<K, V> find(int h, Object k, Class<?> kc) { 7 HashMap.TreeNode<K, V> p = this; 8 do { 9 int ph, dir; 10 K pk; 11 HashMap.TreeNode<K, V> pl = p.left, pr = p.right, q; 12 13 //p的hash > 目標hash, 則查找左子樹,否則右子樹 14 if ((ph = p.hash) > h) 15 p = pl; 16 else if (ph < h) 17 p = pr; 18 //找到則返回 19 else if ((pk = p.key) == k || (k != null && k.equals(pk))) 20 return p; 21 //如果左節點是Null則找右子樹,右節點是Null則找左子樹 22 else if (pl == null) 23 p = pr; 24 else if (pr == null) 25 p = pl; 26 //如果不按照hash比較,則按照比較器比較,查找左子樹還是右子樹 27 else if ((kc != null || 28 (kc = comparableClassFor(k)) != null) && 29 (dir = compareComparables(kc, k, pk)) != 0) 30 p = (dir < 0) ? pl : pr; 31 //如果在右子樹找到則直接返回 32 else if ((q = pr.find(h, k, kc)) != null) 33 return q; 34 //否則在左子樹查找 35 else 36 p = pl; 37 } while (p != null); 38 //否則返回Null 39 return null; 40 }
六、Function tieBreakOrder
1 /** 2 * 在像紅黑樹插入即節點的時候,為了確定相同hashCode的節點插入的順序, 3 * 設定了插入順序的規則,結果一定是不想等的。非左即右。 4 */ 5 static int tieBreakOrder(Object a, Object b) { 6 int d; 7 8 //會對兩個類名相等的類進行比較 9 if (a == null || b == null || 10 (d = a.getClass().getName(). 11 compareTo(b.getClass().getName())) == 0) 12 //返回兩個類內存地址的hashCode比較結果,小的或者相都是-1,否則1,並非是類的hashCode的比較 13 d = (System.identityHashCode(a) <= System.identityHashCode(b) ? 14 -1 : 1); 15 return d; 16 }
七、Function rotateLeft, 左旋
圖片來源(https://blog.csdn.net/sun_tttt/article/details/65445754)
1 static <K, V> HashMap.TreeNode<K, V> rotateLeft(HashMap.TreeNode<K, V> root, 2 HashMap.TreeNode<K, V> p) { 3 //三個節點,右節點,parent的parent節點,右的左節點 4 HashMap.TreeNode<K, V> r, pp, rl; 5 //該節點不為null並且右節點不為null 6 if (p != null && (r = p.right) != null) { 7 //因為是左旋,所以如果右節點的左節點如果不為null,則rl的根節點設為p 8 if ((rl = p.right = r.left) != null) 9 rl.parent = p; 10 //如果左旋后的頭節點為根節點,則根據紅黑樹的性質,顏色為黑色 11 if ((pp = r.parent = p.parent) == null) 12 (root = r).red = false; 13 //因為是左旋,所以p的位置由pr取代,所以p的parent節點的p位置設為現在pr的位置。 14 else if (pp.left == p) 15 pp.left = r; 16 else 17 pp.right = r; 18 //然后r的left是p,p的父節點是r 19 r.left = p; 20 p.parent = r; 21 } 22 return root; 23 }
八、Function rightRotate, 右旋
圖片來源(https://blog.csdn.net/sun_tttt/article/details/65445754)
1 static <K, V> HashMap.TreeNode<K, V> rotateRight(HashMap.TreeNode<K, V> root, 2 HashMap.TreeNode<K, V> p) { 3 //定義3個節點,左節點,頭節點的頭節點,左節點的右節點 4 HashMap.TreeNode<K, V> l, pp, lr; 5 //要旋轉的節點和左節點不為空時 6 if (p != null && (l = p.left) != null) { 7 //根據右旋,(原)頭節點的左節點為原先左節點的右節點,並且把其父節點設為原頭節點,即p 8 if ((lr = p.left = l.right) != null) 9 lr.parent = p; 10 //同樣,如果現在的頭節點為根節點的話,標記節點的顏色為黑色 11 if ((pp = l.parent = p.parent) == null) 12 (root = l).red = false; 13 //頭節點的頭節點設定其自節點 14 else if (pp.right == p) 15 pp.right = l; 16 else 17 pp.left = l; 18 //同樣,根據右旋,指定現在的頭節點的右節點為原先的頭節點,原先的頭節點的父節點為現在的頭節點 19 l.right = p; 20 p.parent = l; 21 } 22 return root; 23 }
九、Function balanceInsertion 代碼和圖結合看很好理解過程,至於為什么會平衡還要分析。
圖片來源https://blog.csdn.net/weixin_42340670/article/details/80550932
1.無旋轉
2.旋轉情況1
3.旋轉情況2
1 /* 2 保證插入節點后,紅黑樹仍然是平衡的,代碼很長,結合圖看更好理解點,這樣看太抽象了。但是雖然邏輯復雜,但是足以 3 見證紅黑樹的高效性,因為更新樹的話只是旋轉操作,即改變下指針的位置,並且設置一下節點的位置就可以了 4 */ 5 static <K, V> HashMap.TreeNode<K, V> balanceInsertion(HashMap.TreeNode<K, V> root, 6 HashMap.TreeNode<K, V> x) { 7 //先把節點設為紅色 8 x.red = true; 9 //定義四個節點 10 for (HashMap.TreeNode<K, V> xp, xpp, xppl, xppr; ; ) { 11 //如果x是根節點,則把它設為黑色,並返回根節點 12 if ((xp = x.parent) == null) { 13 x.red = false; 14 return x; 15 } 16 //如果x的父節點即xp是黑色,並且xp為根節點,則返回,什么也不做。 17 else if (!xp.red || (xpp = xp.parent) == null) 18 return root; 19 //如果xp為xp父節點的左節點 20 if (xp == (xppl = xpp.left)) { 21 //如果xpp的右節點非空並且是紅色的,那么把其設為黑色,xpp的左節點也設為黑色,xpp設為紅色,並且x等於xpp 22 if ((xppr = xpp.right) != null && xppr.red) { 23 xppr.red = false; 24 xp.red = false; 25 xpp.red = true; 26 x = xpp; 27 } 28 //如果xpp的右節點是空或者為黑色的話 29 else { 30 //如果x是xp的右節點,那么左旋xp節點,並且重新更新xp和xpp 31 if (x == xp.right) { 32 root = rotateLeft(root, x = xp); 33 xpp = (xp = x.parent) == null ? null : xp.parent; 34 } 35 //如果x的父節點不為空,先把它設為黑色 36 if (xp != null) { 37 xp.red = false; 38 //如果xp的父節點不為空,則先把xpp設為紅色,然后再右旋 39 if (xpp != null) { 40 xpp.red = true; 41 root = rotateRight(root, xpp); 42 } 43 } 44 } 45 } 46 //如果xp為xp父節點的右右節點 47 else { 48 //如果xpp的左節點非空並且是紅色的話,把xppl設為黑色,xp設為黑色,xp的父節點設為紅色 49 if (xppl != null && xppl.red) { 50 xppl.red = false; 51 xp.red = false; 52 xpp.red = true; 53 x = xpp; 54 } 55 //如果xpp的左節點是空或者是黑色的話 56 else { 57 //如果x為父節點的左節點,則右旋xp節點,並重新設置xp,xpp 58 if (x == xp.left) { 59 root = rotateRight(root, x = xp); 60 xpp = (xp = x.parent) == null ? null : xp.parent; 61 } 62 //如果x的父節點為空, 63 if (xp != null) { 64 //先把其設為黑色 65 xp.red = false; 66 //如果xp的父節點不為空,則xpp設為紅色,並左旋xpp節點 67 if (xpp != null) { 68 xpp.red = true; 69 root = rotateLeft(root, xpp); 70 } 71 } 72 } 73 } 74 } 75 }
十、Function treeify
1 /** 2 * 把鏈表生成紅黑樹,返回頭節點 3 */ 4 final void treeify(HashMap.Node<K, V>[] tab) { 5 HashMap.TreeNode<K, V> root = null; 6 7 //兩個指針,一個是鏈表的表頭,一個是下一個指針 8 for (HashMap.TreeNode<K, V> x = this, next; x != null; x = next) { 9 next = (HashMap.TreeNode<K, V>) x.next; 10 x.left = x.right = null; 11 12 //先設定 root為頭節點,parent為null,根節點為黑色, 13 if (root == null) { 14 x.parent = null; 15 x.red = false; 16 root = x; 17 } else { 18 K k = x.key; 19 int h = x.hash; 20 Class<?> kc = null; 21 22 //遍歷紅黑樹 23 for (HashMap.TreeNode<K, V> p = root; ; ) { 24 int dir, ph; 25 K pk = p.key; 26 //如果當前樹節點的hash > 鏈表節點的hash則dir值為-1 27 if ((ph = p.hash) > h) 28 dir = -1; 29 //否則為1 30 else if (ph < h) 31 dir = 1; 32 //如果不按照hash值比較的話,並且比較器不存在或者比較器比較的值是0的話,則把死結打開 33 else if ((kc == null && 34 (kc = comparableClassFor(k)) == null) || 35 (dir = compareComparables(kc, k, pk)) == 0) 36 dir = tieBreakOrder(k, pk); 37 //設置一個紅黑樹的節點 38 HashMap.TreeNode<K, V> xp = p; 39 //設置節點的走向,如果dir <= 0則p為做節點,否則為右,也就是找到鏈表節點應該插入的位置 40 if ((p = (dir <= 0) ? p.left : p.right) == null) { 41 //設置鏈表節點的父節點 42 x.parent = xp; 43 if (dir <= 0) 44 xp.left = x; 45 else 46 xp.right = x; 47 //插入節點,並且不破壞紅黑樹的性質 48 root = balanceInsertion(root, x); 49 break; 50 } 51 } 52 } 53 } 54 //設置頭節點 55 moveRootToFront(tab, root); 56 }
十一、Function split
1 /** 2 * 樹減枝, 3 * 只有在resize的時候才調用該方法 4 * @param map the map 5 * @param tab 新的table 6 * @param index 老的table的index 7 * @param bit oldCap 8 */ 9 final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) { 10 TreeNode<K,V> b = this; 11 // Relink into lo and hi lists, preserving order 12 TreeNode<K,V> loHead = null, loTail = null; 13 TreeNode<K,V> hiHead = null, hiTail = null; 14 // 如果size很小的話則把樹變成鏈表,用lc和hc來計數 15 int lc = 0, hc = 0; 16 // 遍歷紅黑樹 17 for (TreeNode<K,V> e = b, next; e != null; e = next) { 18 next = (TreeNode<K,V>)e.next; 19 e.next = null; 20 // 判斷將樹的節點歸為哪一部分 21 if ((e.hash & bit) == 0) { 22 if ((e.prev = loTail) == null) 23 loHead = e; 24 else 25 loTail.next = e; 26 loTail = e; 27 ++lc; 28 } 29 else { 30 if ((e.prev = hiTail) == null) 31 hiHead = e; 32 else 33 hiTail.next = e; 34 hiTail = e; 35 ++hc; 36 } 37 } 38 39 //lo這部分樹放入的位置,index即原先的位置 40 if (loHead != null) { 41 if (lc <= UNTREEIFY_THRESHOLD) 42 tab[index] = loHead.untreeify(map); 43 else { 44 tab[index] = loHead; 45 if (hiHead != null) // (else is already treeified) 46 loHead.treeify(tab); 47 } 48 } 49 //index+bit這部分改變了 50 if (hiHead != null) { 51 if (hc <= UNTREEIFY_THRESHOLD) 52 tab[index + bit] = hiHead.untreeify(map); 53 else { 54 tab[index + bit] = hiHead; 55 if (loHead != null) 56 hiHead.treeify(tab); 57 } 58 } 59 }