Map原始碼解析之HashMap紅黑樹
Map原始碼解析之HashMap 上一篇文章分析了HashMap的原始碼,但關於紅黑樹的部分都是粗略帶過,這一篇文章則將著重分析HashMap中和紅黑樹相關的邏輯程式碼。 紅黑樹的理論知識可以參考紅黑樹(一)之 原理和演算法詳細介紹進行了解。
一. 紅黑樹的特性
(1) 每個節點或者是黑色,或者是紅色。 (2) 根節點是黑色。 (3) 每個葉子節點(空節點,NIL節點)是黑色。 (4) 如果一個節點是紅色的,則它的子節點必須是黑色的。 (5) 從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。
二. 基礎方法
1. 左旋 TreeNode#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; }
主要分3步,p為支點節點,r為p節點的右兒子。 (1)r節點的左二子成為p節點的右兒子。 (2)p節點的父節點成為r節點的父節點。如果原來p節點為根節點,那麼此時r節點成為了根節點,需要將顏色置為黑色。 (3)p節點成為r節點的左兒子。
2. 右旋TreeNode#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; }
邏輯和左旋差不多,只不過旋轉的方向不同,分3步,p為支點節點,l為p節點的左兒子。 (1)l節點的右兒子成為p節點的左兒子。 (2)p節點的父節點成為l節點的父節點。如果原來p節點為根節點,那麼此時l節點成為了根節點,需要將顏色置為黑色。 (3)p節點成為l節點的右兒子。
3. TreeNode#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;
TreeNode<K,V> rp = root.prev;
if ((rn = root.next) != null)
((TreeNode<K,V>)rn).prev = rp;
if (rp != null)
rp.next = rn;
if (first != null)
first.prev = root;
root.next = first;
root.prev = null;
}
assert checkInvariants(root);
}
}
方法並不複雜,如果第一個節點並不是根節點,使得根節點first的前一個節點和後一個節點進行連線,然後根節點作為first節點的前一個節點。
4. treeNode#root方法
遍歷找到紅黑樹的根節點。
final TreeNode<K,V> root() {
for (TreeNode<K,V> r = this, p;;) {
if ((p = r.parent) == null)
return r;
r = p;
}
}
5. HashMap#comparableClassFor方法
如果x的型別形如class C implements Comparable,則返回型別,否則返回null。
static Class<?> comparableClassFor(Object x) {
if (x instanceof Comparable) {
Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
if ((c = x.getClass()) == String.class) // bypass checks
return c;
if ((ts = c.getGenericInterfaces()) != null) {
for (int i = 0; i < ts.length; ++i) {
if (((t = ts[i]) instanceof ParameterizedType) &&
((p = (ParameterizedType)t).getRawType() ==
Comparable.class) &&
(as = p.getActualTypeArguments()) != null &&
as.length == 1 && as[0] == c) // type arg is c
return c;
}
}
}
return null;
}
6. HashMap#compareComparables方法
使用HashMap#comparableClassFor方法返回的comparable類比較大小。
static int compareComparables(Class<?> kc, Object k, Object x) {
return (x == null || x.getClass() != kc ? 0 :
((Comparable)k).compareTo(x));
}
三. 樹化
1. HashMap#treeifyBin方法
上一篇文章已經講到在HashMap#putVal方法中當加入節點後連結串列長度超過樹化閾值,會呼叫HashMap#treeify方法準備樹化。
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
在方法中,分兩種情況進行處理: (1)容量(即陣列長度)小於最小樹化容量,進行擴容。 (2)容量(即陣列長度)不小於最小樹化容量,將節點Node轉化為TreeNode,並將連結串列轉化為雙向連結串列,然後呼叫TreeNode#treeify方法進行樹化。
2. TreeNode#treeify方法
TreeNode#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;
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);
}
可以看到從連結串列的頭結點開始進行遍歷。 (1)如果是第一個節點(即根節點不存在),將該節點作為紅黑樹的根節點,節點顏色置為黑色。 (2)如果不是第一個節點(即存在根節點),由於紅黑樹按照節點的hash值進行排序的,因此從根節點開始作為參照節點,根據目標節點和參照節點的hash值的比較結果dir進入對應的子節點,並以對應的子節點作為參照節點繼續進行迴圈,一直到參照節點不存在子節點為止。將目標節點作為參照節點的子節點插入,然後呼叫balanceInsertion方法對紅黑樹進行修正。 (3)遍歷完成後,呼叫moveRootToFront方法保證根節點是第一個節點。
3. TreeNode#balanceInsertion方法
TreeNode#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;;) {
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
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);
}
}
}
}
}
}
可以看到,我們首先預設將節點x顏色置為紅色,因為插入一個紅色節點,不會違背“從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點”的特性。 接下來,我們分情況對紅黑樹進行處理,使得其不違背其它特性。 (1)x節點是根節點,顏色置為黑色,符合所有特性,結束流程。 (2)x節點的父節點是黑色節點,符合所有特性,結束流程。 (3)x節點的父節點是紅色,需要進行處理以保證其符合“如果一個節點是紅色的,則它的子節點必須是黑色的”特性,又細分為一下情況 (3.1)父節點是祖父節點的左節點 (3.1.1)叔父節點(祖父節點的另一個子節點)為紅色 父節點和叔父節點置為黑色,祖父節點置為紅色,祖父節點作為當前節點繼續迴圈處理。 (3.1.2)叔父節點為黑色(叔父節點不存在即NIL按照紅黑樹視為黑色節點) 首先,如果當前節點是父節點的右兒子,父節點作為當前節點,並以其為支點進行左旋。 其次,如果當前節點的父節點存在,父節點顏色置為黑色。注意這一步的當前節點已經和上一步的而當前節點不同。 然後,如果當前節點的祖父節點存在,祖父節點置為紅色,並以祖父節點為支點節點進行右旋。 (3.2)父節點是祖父節點的右兒子 (3.2.1)叔父節點為紅色 同3.1.1的處理 (3.2.2)叔父節點為黑色(叔父節點不存在即NIL按照紅黑樹視為黑色節點) 與3.1.2也類似 首先,如果當前節點時父節點的左兒子,父節點作為當前節點,並以其為支點進行右旋。 其次,如果當前節點的父節點存在,父節點顏色置為黑色。注意這一步的當前節點已經和上一步的而當前節點不同。 然後,如果當前節點的祖父節點存在,祖父節點置為紅色,並以祖父節點為支點節點進行左旋。 以3.1.2的場景舉例,如下圖所示
四. 查詢節點
1. TreeNode#getTreeNode方法
上一篇已經分析過,在HashMap#getNode方法中,如果陣列的某一個元素是紅黑樹,會呼叫TreeNode#getTreeNode方法進行查詢。
final TreeNode<K,V> getTreeNode(int h, Object k) {
return ((parent != null) ? root() : this).find(h, k, null);
}
呼叫根節點的find方法進行查詢。
2.TreeNode#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;
if ((ph = p.hash) > h)
p = pl;
else if (ph < h)
p = pr;
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;
else if (pl == null)
p = pr;
else if (pr == null)
p = pl;
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr;
//如果key與當前節點的key值大小相等但不相同,先嚐試在右兒子查詢,找到則返回,找不到則在左兒子找
else if ((q = pr.find(h, k, kc)) != null)
return q;
else
p = pl;
} while (p != null);
return null;
}
方法不復雜,其實和普通的二叉查詢樹查詢節點的邏輯一致,從根節點開始作為當前節點開始,用當前節點的key值與key進行比較,進入相應的子節點進行迴圈,知道找到目標節點,否則返回null。
五. 插入/覆蓋節點
1. TreeNode#putTreeVal方法
上一篇文章已經分析到,在HashMap#putVal方法中,如果陣列元素是紅黑樹,會呼叫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;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;
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;
}
}
}
邏輯也不復雜,先按照二叉查詢樹的方式進行遍歷查詢,如果找到則返回對應節點,找不到則新建節點並作為當前節點的子節點,並且作為當前節點的下一個節點。 然後呼叫TreeNode#balanceInsertion方法進行修正保證插入節點後依舊是一顆紅黑樹。 最後呼叫TreeNode#moveRootToFront方法保證根節點是第一個節點。
六. 刪除節點
1. TreeNode#removeTreeNode方法
根據上一篇文章的分析,在HashMap#removeNode方法中如果匹配到的節點時紅黑樹的節點,會呼叫TreeNode#removeTreeNode對當前節點進行刪除。
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;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;
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;
}
}
}
先從理論上描述一下紅黑樹刪除節點的過程,分3種。刪除節點後在修正紅黑樹。
(1)被刪除的節點沒有子節點,直接刪除即可。
(2)被刪除的節點只有一個子節點,直接刪除該節點,用其子節點代替該節點的位置。
(3)被刪除的節點有兩個子節點,會稍微複雜一點,找到其右兒子的最左節點(左兒子的最右節點也能符合要求),兩者交換位置和顏色,因為其右兒子的最左節點大於等於其所有的左分支節點,小於等於所有的右分支節點,二叉查詢樹的性質不會改變,依舊是有序的。交換以後,根據其右兒子的最左節點沒有子節點和只有右節點的情況有分別按照(1)、(2)進行刪除處理即可。
刪除節點後,如果刪除的節點是黑色的,需要進行修正,保證刪除節點後依舊是一顆紅黑樹。
接著我們看程式碼,從程式碼角度分析:
(1)先處理prev和next屬性的問題。
如果被刪除的是第一個節點(正常情況下就是根節點),將其next對應的節點作為第一個節點。
如果不是第一個節點,將其prev對應的節點的next屬性指向其next對應的節點。
如果不是最後一個節點,將其next對應的節點的prev屬性指向其prev對應的節點。
(2)校驗節點
如果已經沒有節點存在,結束流程。
(3)確認根節點
(4)如果紅黑樹節點數量過少(root == null || root.right == null || (rl = root.left) == null || rl.left == null
),需要進行去樹化,結束流程。
(5)接下來處理parent、left、right屬性的問題,將當前節點記為p,代替當前節點在紅黑樹中的位置的後繼節點記為replacement。從這一步才開始我們上面講的刪除紅黑樹節點的過程。
(5.1)如果當前節點左兒子和右兒子都存在,需要找到其右兒子的最左節點,兩者交換位置和顏色
(5.1.1)找到當前節點右兒子的最左節點,記為s。
(5.1.2)當前節點p和s顏色互換。
(5.1.3)當前節點p和s位置互換。
首先進行判斷,如果s是p節點的右兒子,直接將p節點作為s節點的右兒子。否則將p作為s的父節點的子節點,p初步取代s節點的位置,並將p節點的右兒子作為s節點的右兒子,s也初步取代了p的位置
然後,如果s節點有右兒子,將其作為p節點的右兒子,確保p節點完全取代了s節點的位置。
然後,並將p節點的左兒子和父節點作為s節點的左兒子和父節點,如果不存在父節點,則將s作為根節點,確保s節點完全取代了p節點的位置。
最後確定後繼節點replacement,就是現在的p節點(原來的s節點)的右兒子(右兒子存在)或者本身(右兒子不存在)。
(5.2)如果當前節點左兒子存在,右兒子不存在,取左兒子作為後繼節點replacement。
(5.3)如果當前節點左兒子不存在,右兒子存在,取右兒子作為後繼節點replacement。
(5.4)如果當前節點沒有子節點,取當前節點p本身作為replacement。
(6)如果p節點和replacement不相同,即被刪除的節點有子節點,將p節點的子節點和p節點的父節點關聯起來,刪除p節點。
(7)如果p節點是黑色節點,需要呼叫balanceDeletion方法保證刪除後依舊是一顆紅黑樹。
(8)如果p節點和replacement相同,即被刪除的節點沒有子節點,刪除p節點,刪除p節點的父節點中和p對應的子節點資訊。
(9)根據movable判斷是否需要進行moveRootToFront操作,保證根節點是第一個節點。
從上面的程式碼我們可以注意到有一點,如果需要刪除的節點有子節點,則先刪除節點再修正紅黑樹;如果需要刪除的節點沒有子節點,則先修正紅黑樹再刪除節點。因此,傳入balanceDeletion方法的進行修正的當前節點有可能是被刪除的,也有可能是被保留的。
2. TreeNode#balanceDeletion方法
對刪除節點後的紅黑樹進行修正,保證其依舊是一顆紅黑樹。 有兩個引數,第一個表示刪除前的紅黑樹的根節點,第二個表示需要進行修正的當前節點,返回修正後的根節點。
static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
TreeNode<K,V> x) {
for (TreeNode<K,V> xp, xpl, xpr;;) {
if (x == null || x == root)
return root;
//經過之前的迴圈調整後,有可能根節點發生了變化,root指向的節點不再是根節點,而x指向的是根節點
else if ((xp = x.parent) == null) {
x.red = false;
return x;
}
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;
}
}
}
}
}
對於刪除節點的處理,從當前節點開始迴圈,同樣分幾種情況: (1)x不存在或x是根節點,置為黑色,結束流程,對應場景6。 (2)x是紅色節點,置為黑色,結束流程,對應場景3。 (3)x是黑色節點且不為根節點:再次劃分情況 (3.1)x是父節點的左兒子 首先,如果x的兄弟節點是紅色,此時父節點和兄弟節點的子節點肯定是黑色。將兄弟節點置為黑色,父節點置為紅色,以父節點為支點進行右旋,對應場景1. 接著進行判斷 (3.2.1)如果此時x的兄弟節點不存在,以x的父節點作為當前節點繼續下一步迴圈。 (3.2.2)如果x的兄弟節點的子節點都是黑色(NIL視為子節點),x的兄弟節點置為紅色,以x的父節點作為當前節點繼續下一步迴圈,對應場景2。否則進入(3.2.3)。 (3.2.3)兄弟節點的右兒子為黑色,那麼將兄弟節點的左兒子置為黑色,兄弟節點置為紅色,以兄弟節點為支點進行右旋,重新取當前節點x的兄弟節點,對應場景4。 (3.2.4)如果兄弟節點存在,將兄弟節點的顏色和父節點保持一致,如果兄弟節點的右兒子存在,將兄弟節點的右兒子置為黑色,父節點置為黑色,以父節點為支點左旋,將根節點作為當前節點,對應場景5。 (3.2)x是父節點的右兒子,邏輯與(3.1)基本一致,不再展開。 總結一下,共有下面6種場景,和上述步驟對應。
七. 去樹化
1. TreeNode#untreeify方法
當紅黑樹節點數量過少時,會進行去樹化。
主要是當陣列擴容和刪除節點時可能會觸發去樹化。
陣列擴容時,節點數小於去樹化閾值(6)時,會觸發去樹化;刪除紅黑樹節點時,如果(root == null || root.right == null || (rl = root.left) == null || rl.left == null
)會觸發去樹化。
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) {
Node<K,V> p = map.replacementNode(q, null);
if (tl == null)
hd = p;
else
tl.next = p;
tl = p;
}
return hd;
}
從紅黑樹的第一個節點開始遍歷,將TreeeNode替換成Node,並組裝成連結串列。
八. 紅黑樹移植
在HashMap擴容過程中,如果陣列對應下標的節點是TreeNode,需要呼叫HashMap#split方法進行移植。
1. HashMap#split方法
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);
}
}
}
HashMap#split方法分兩部分 (1)和連結串列的移植邏輯類似,遍歷並組裝層兩條連結串列 (2)根據連結串列的長度和樹化閾值進行比較,連結串列長度大於樹化閾值,則呼叫TreeNode#treeify方法進行樹化,否則呼叫TreeNode#untreeify進行去樹化。