HashMap中的TreeNode,红黑树源码分析

Posted ylxn

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HashMap中的TreeNode,红黑树源码分析相关的知识,希望对你有一定的参考价值。

在看HashMap的源码时候看到了TreeNode。因此需要对其进行一个了解。是一个红黑树。可以百度一下红黑树的数据结构。分析了下源码,还是比较枯燥的

 

红黑树的性质:本身是一个二叉查找树(所有左节点的值都比右节点的小)。另:

  1. 节点是红色或者黑色
  2. 根节点是黑色
  3. 每个叶节点(Nil节点,空节点)是黑色的
  4. 每个红节点对应的两个子节点都是黑色的(不可能有两个相连的红节点)。
  5. 从任意节点出发,到每个叶子节点都有相同的黑色节点。

这保证了红黑数是平衡的,从根到叶子的最长的可能路径不多于最短的可能路径的两倍长,因此插入、删除查找的最坏情况是有所保证的,与树的高度成正比。原因有两点:

  1. 最短的可能路径都是黑色节点。
  2. 最长的路径是红黑节点交替的路径。

因为从同一节点出发,到每一个叶子有相同的黑色节点,所以保证了最长路径是最短路径的两杯长。

一、成员变量与构造函数

 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         }

 

以上是关于HashMap中的TreeNode,红黑树源码分析的主要内容,如果未能解决你的问题,请参考以下文章

HashMap源码分析及原理分析

HashMap红黑树解析

HashMap红黑树原理及源码分析---图形注释一应俱全

HashMap红黑树原理及源码分析---图形注释一应俱全

面试常问的HashMap源码分析(jdk1.8)

HashMap源码分析