前面几篇文章已经讲解过HashMap内部实现以及红黑树的基础知识,今天这篇文章就讲解之前HashMap中未讲解的红黑树操作部分,如果没了解红黑树,请去阅读前面的两篇文章,能更好的理解本章所讲解的红黑树源码操作,全文默认读者已经了解红黑树的相关知识,接下来,就以HashMap.TreeNode来说明红黑树的源码操作。

前言

jdk版本:1.8

以HashMap.TreeNode为例是因为之前HashMap的红黑树操作在文章省略了,这里进行一个解释,其实源码里并不是只有这个地方用红黑树结构,但是总体上都大同小异,故只说明这一部分就好,举一反三的能力相信各位都应该拥有。

类定义

  1. static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V>

继承LinkedHashMap.Entry,追溯源头可以到HashMap.Node,下面图展示了对应的结构关系:

属性

  1. /**
  2. * 父节点
  3. */
  4. TreeNode<K,V> parent; // red-black tree links
  5. /**
  6. * 左子节点
  7. */
  8. TreeNode<K,V> left;
  9. /**
  10. * 右子节点
  11. */
  12. TreeNode<K,V> right;
  13. /**
  14. * 指向前一个节点
  15. */
  16. TreeNode<K,V> prev; // needed to unlink next upon deletion
  17. /**
  18. * 是否是红色节点
  19. */
  20. boolean red;

以上的属性都是为了满足红黑树特性而设置

构造方法

  1. /**
  2. * 构造方法直接调用的父类方法
  3. * 最终还是HashMap.Node的构造方法,调用代码下面也列出来了
  4. */
  5. TreeNode(int hash, K key, V val, Node<K,V> next) {
  6. super(hash, key, val, next);
  7. }
  8. /**
  9. * HashMap.Node subclass for normal LinkedHashMap entries.
  10. */
  11. static class Entry<K,V> extends HashMap.Node<K,V> {
  12. Entry<K,V> before, after;
  13. Entry(int hash, K key, V value, Node<K,V> next) {
  14. super(hash, key, value, next);
  15. }
  16. }
  17. Node(int hash, K key, V value, Node<K,V> next) {
  18. this.hash = hash;
  19. this.key = key;
  20. this.value = value;
  21. this.next = next;
  22. }

Node和TreeNode相同的构造方法

重要方法

root

实现上非常简单,不断向上遍历,找到父节点为空的节点即为根节点

  1. /**
  2. * 返回根节点TreeNode
  3. */
  4. final TreeNode<K,V> root() {
  5. for (TreeNode<K,V> r = this, p;;) {
  6. if ((p = r.parent) == null)
  7. return r;
  8. r = p;
  9. }
  10. }

moveRootToFront

从注释中也能看出当前方法的含义,确保根节点是bin桶(数组tab的其中一个元素)中的第一个节点,如果不是,则进行操作,将根节点放到tab数组上,这个跟HashMap结构有关,数组(链表+红黑树),在进行树化,结构调整时,根节点可能会变化,原有数组tab对应索引指向的树节点需要进行改变,指向新的根节点,这里注意下,移动时不是修改红黑树是修改的链表结构,prev和next属性

  1. /**
  2. * Ensures that the given root is the first node of its bin.
  3. */
  4. static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
  5. int n;
  6. if (root != null && tab != null && (n = tab.length) > 0) {
  7. // 找到当前树所在的bin桶位置(即数组tab的位置)
  8. int index = (n - 1) & root.hash;
  9. // 将tab[index]的树节点记录下来为first
  10. TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
  11. // root没有落在tab数组上,修改为root在tab数组上
  12. if (root != first) {
  13. Node<K,V> rn;
  14. // 这里即替换掉tab[index]指向的原有节点,可以理解成现在指向root节点
  15. tab[index] = root;
  16. // rp为root指向的前一个节点
  17. TreeNode<K,V> rp = root.prev;
  18. // rn为root的后一个节点
  19. // 将root前后节点关联
  20. if ((rn = root.next) != null)
  21. ((TreeNode<K,V>)rn).prev = rp;
  22. if (rp != null)
  23. rp.next = rn;
  24. // first 和 root 节点进行关联,first的前一个节点为root
  25. if (first != null)
  26. first.prev = root;
  27. // 修改root的链表属性
  28. root.next = first;
  29. root.prev = null;
  30. }
  31. // 检查红黑树一致性
  32. assert checkInvariants(root);
  33. }
  34. }

find

从当前节点开始使用给定的hash和key查找到对应的节点,只会判断当前节点为根节点的局部树结构。这里复习下整个HashMap查找过程,通过(length - 1) & hash 判断bin桶位置(数组中的位置),这里不是hash值相等,注意再判断该位置处是什么类型,链表还是红黑树,链表类型,循环next遍历,直到key值相等。红黑树类型 递归左右子树遍历,直到key值相等。

  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. * kc : key class
  6. */
  7. final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
  8. TreeNode<K,V> p = this;
  9. do {
  10. // ph:p的hash值
  11. int ph, dir; K pk;
  12. TreeNode<K,V> pl = p.left, pr = p.right, q;
  13. // 查找节点hash值小于p的hash值,搜索左子树
  14. if ((ph = p.hash) > h)
  15. p = pl;
  16. // 查找节点hash值大于p的hash值,搜索右子树
  17. else if (ph < h)
  18. p = pr;
  19. // key值相同,说明就是此p节点
  20. else if ((pk = p.key) == k || (k != null && k.equals(pk)))
  21. return p;
  22. // 若hash相等但key不等,向左右子树非空的一侧移动
  23. else if (pl == null)
  24. p = pr;
  25. else if (pr == null)
  26. p = pl;
  27. // 左右两边都不为空
  28. // 判断kc是否是k实现的可比较的类,是就比较k和pk的值,k<pk向左子树移动否则向右子树移动
  29. // hash相等,key不等,使用key实现的compareTo判断大小
  30. else if ((kc != null ||
  31. (kc = comparableClassFor(k)) != null) &&
  32. (dir = compareComparables(kc, k, pk)) != 0)
  33. p = (dir < 0) ? pl : pr;
  34. // 上面所有情况依旧不能判断左右,就先递归判断右子树,看是否匹配上,匹配上就赋右子树,否则左子树
  35. else if ((q = pr.find(h, k, kc)) != null)
  36. return q;
  37. else
  38. p = pl;
  39. } while (p != null);
  40. return null;
  41. }

tieBreakOrder

比较两个对象的大小,返回值只能大于0或小于0,不能为0,因为需要插入节点是放在左子树还是右子树,这里在两个对象都不为空时,先比较两个对象的类名按字符串规则比较,如果类名比较不出来或者为空则调用native方法去比较hashcode值,相等时返回-1

  1. static int tieBreakOrder(Object a, Object b) {
  2. int d;
  3. if (a == null || b == null ||
  4. (d = a.getClass().getName().
  5. compareTo(b.getClass().getName())) == 0)
  6. d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
  7. -1 : 1);
  8. return d;
  9. }

treeify

以当前TreeNode为初始节点,循环处理链表上的每个节点,每次插入树节点都要进行平衡处理,保证红黑树的平衡。

  1. /**
  2. * Forms tree of the nodes linked from this node.
  3. * @return root of tree
  4. */
  5. final void treeify(Node<K,V>[] tab) {
  6. TreeNode<K,V> root = null;
  7. // this当前TreeNode,一般应该以树的根节点为初始值,根据链表进行遍历
  8. for (TreeNode<K,V> x = this, next; x != null; x = next) {
  9. next = (TreeNode<K,V>)x.next;
  10. x.left = x.right = null;
  11. // 首次root为空 当前节点先设置为root 节点颜色为黑色
  12. if (root == null) {
  13. x.parent = null;
  14. x.red = false;
  15. root = x;
  16. }
  17. // root节点设置之后开始将链表节点依次插入处理,x=x.next插入root为根节点的红黑树,循环处理
  18. else {
  19. K k = x.key;
  20. int h = x.hash;
  21. Class<?> kc = null;
  22. for (TreeNode<K,V> p = root;;) {
  23. int dir, ph;
  24. K pk = p.key;
  25. // 比较hash值判断处于左右子树哪侧
  26. if ((ph = p.hash) > h)
  27. // 节点处于p左子树下
  28. dir = -1;
  29. else if (ph < h)
  30. // 节点处于p右子树下
  31. dir = 1;
  32. else if ((kc == null &&
  33. (kc = comparableClassFor(k)) == null) ||
  34. (dir = compareComparables(kc, k, pk)) == 0)
  35. // hash值相等根据compareTo判断,判断不出来继续执行tieBreakOrder判断
  36. dir = tieBreakOrder(k, pk);
  37. // x的父节点设置为xp
  38. TreeNode<K,V> xp = p;
  39. // 左右节点为空,说明可以将新增节点插入
  40. // 非空,继续循环子树,p指向左子树或右子树,继续循环判断直到为空的节点
  41. if ((p = (dir <= 0) ? p.left : p.right) == null) {
  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. // root位置可能会在调整中变更,这里需调用确保根节点落在tab数组上
  55. moveRootToFront(tab, root);
  56. }

untreeify

将树转换为链表结构,将TreeNode转化为Node,改变next指向即可

  1. /**
  2. * Returns a list of non-TreeNodes replacing those linked from
  3. * this node.
  4. */
  5. final Node<K,V> untreeify(HashMap<K,V> map) {
  6. // hd是链表首节点,tl是链表尾节点
  7. Node<K,V> hd = null, tl = null;
  8. for (Node<K,V> q = this; q != null; q = q.next) {
  9. Node<K,V> p = map.replacementNode(q, null);
  10. if (tl == null)
  11. hd = p;
  12. else
  13. tl.next = p;
  14. tl = p;
  15. }
  16. return hd;
  17. }
  18. Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
  19. return new Node<>(p.hash, p.key, p.value, next);
  20. }

putTreeVal

在红黑树结构中的putVal操作,先判断节点应该存在的位置,循环判断左右子树,找到应该存在的位置。若该位置为空,相当于原本无值,插入节点,然后进行平衡,桶位置调整。若该位置有值且相等,则直接返回,不需要插入操作。

  1. /**
  2. * Tree version of putVal.
  3. */
  4. final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
  5. int h, K k, V v) {
  6. Class<?> kc = null;
  7. boolean searched = false;
  8. // 获取根节点
  9. TreeNode<K,V> root = (parent != null) ? root() : this;
  10. // 循环遍历,类似find方法
  11. for (TreeNode<K,V> p = root;;) {
  12. int dir, ph; K pk;
  13. if ((ph = p.hash) > h)
  14. dir = -1;
  15. else if (ph < h)
  16. dir = 1;
  17. else if ((pk = p.key) == k || (k != null && k.equals(pk)))
  18. return p;
  19. else if ((kc == null &&
  20. (kc = comparableClassFor(k)) == null) ||
  21. (dir = compareComparables(kc, k, pk)) == 0) {
  22. if (!searched) {
  23. // 递归左右子树进行查找是否已经存在
  24. // 只需判断一次即可,第二次不再执行
  25. TreeNode<K,V> q, ch;
  26. searched = true;
  27. if (((ch = p.left) != null &&
  28. (q = ch.find(h, k, kc)) != null) ||
  29. ((ch = p.right) != null &&
  30. (q = ch.find(h, k, kc)) != null))
  31. return q;
  32. }
  33. // 上面都比较不出来,通过这个方法比较出来
  34. dir = tieBreakOrder(k, pk);
  35. }
  36. // 判断当前插入位置是否为空,为空才插入,非空则继续判断,根据dir判断是左还是右子树
  37. TreeNode<K,V> xp = p;
  38. if ((p = (dir <= 0) ? p.left : p.right) == null) {
  39. Node<K,V> xpn = xp.next;
  40. TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
  41. if (dir <= 0)
  42. xp.left = x;
  43. else
  44. xp.right = x;
  45. xp.next = x;
  46. x.parent = x.prev = xp;
  47. if (xpn != null)
  48. ((TreeNode<K,V>)xpn).prev = x;
  49. // balanceInsertion 插入调整平衡
  50. // moveRootToFront 确保root节点落在tab数组上为首节点
  51. moveRootToFront(tab, balanceInsertion(root, x));
  52. return null;
  53. }
  54. }
  55. }

removeTreeNode

删除树节点操作,movable:判断是否需要调整root节点(放置在tab上),在HashMap里removeNode方法中使用,对应的删除节点进行调用,具体自行查看源码部分。其实,想下,删除操作相当于将链表节点之间的关联重新梳理,修正两部分,第一部分是链表的关系修正,第二部分是树的关系修正,然后自平衡操作即可。


  1. final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,boolean movable) {
  2. int n;
  3. // 判断非空
  4. if (tab == null || (n = tab.length) == 0)
  5. return;
  6. // 计算当前树节点所在的桶的位置(tab索引位置)
  7. int index = (n - 1) & hash;
  8. TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
  9. // succ指向当前删除节点的后一个节点,pred指向当前删除节点的前一个节点
  10. TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
  11. if (pred == null)
  12. // 前一个节点为空,说明删除的节点为根节点,first和tab[index]需指向删除节点的后一个节点
  13. tab[index] = first = succ;
  14. else
  15. // 前一个节点不为空,则前一个节点的后一个节点为succ(删除之后的关联)
  16. pred.next = succ;
  17. if (succ != null)
  18. // 后一个节点不为空,则后一个节点的前一个节点为pred,构建关联
  19. succ.prev = pred;
  20. if (first == null)
  21. // first为空则表明当前桶内无节点,直接return
  22. return;
  23. if (root.parent != null)
  24. // 目前的root节点有父类,需要调整
  25. root = root.root();
  26. if (root == null || root.right == null ||
  27. (rl = root.left) == null || rl.left == null) {
  28. // 根自身或者左右儿子其中一个为空或左子树的子树为空需转换为链表结构
  29. // 考虑到红黑树特性,这几种情况下,节点较少,进行树向链表的转化
  30. tab[index] = first.untreeify(map); // too small
  31. return;
  32. }
  33. // p指向删除的节点
  34. // 上面修正链表的关系,下面修正树中的关系
  35. TreeNode<K,V> p = this, pl = left, pr = right, replacement;
  36. // 删除节点的左右子树都不为空,参考红黑树删除4种情况
  37. if (pl != null && pr != null) {
  38. TreeNode<K,V> s = pr, sl;
  39. while ((sl = s.left) != null) // find successor
  40. // 寻找右子树中最左的叶结点作为后继节点,s指向后继节点
  41. s = sl;
  42. // 交换后继节点和删除节点的颜色,从这里开始在后继节点上进行删除处理
  43. boolean c = s.red; s.red = p.red; p.red = c; // swap colors
  44. TreeNode<K,V> sr = s.right;
  45. TreeNode<K,V> pp = p.parent;
  46. if (s == pr) { // p was s's direct parent
  47. // s是p的右儿子,直接父子关系,交换p和s的位置
  48. // 改变两节点的指向,相当于交换值
  49. p.parent = s;
  50. s.right = p;
  51. }
  52. else {
  53. TreeNode<K,V> sp = s.parent;
  54. // 删除节点的父节点指向其后继节点的父节点
  55. if ((p.parent = sp) != null) {
  56. // 判断s是左子节点还是右子节点,s父节点一侧指向删除节点p
  57. if (s == sp.left)
  58. sp.left = p;
  59. else
  60. sp.right = p;
  61. }
  62. if ((s.right = pr) != null)
  63. // s右节点指向原本p的右节点,不为空,原p右子节点的父节点指向s
  64. pr.parent = s;
  65. }
  66. // 修改p的左节点为空,因为现在p已经在后继节点上
  67. p.left = null;
  68. // 两个if是调整p和s交换后节点的指向关系
  69. if ((p.right = sr) != null)
  70. sr.parent = p;
  71. if ((s.left = pl) != null)
  72. pl.parent = s;
  73. if ((s.parent = pp) == null)
  74. // p的父亲成为s的父亲,为空说明是根节点,root指向s
  75. root = s;
  76. else if (p == pp.left)
  77. // p的父亲的左儿子原为p,现为s
  78. pp.left = s;
  79. else
  80. // 否则右儿子为s
  81. pp.right = s;
  82. // 若s结点有右儿子(s没有左儿子,因为s是后继节点),则replacement为这个右儿子否则为p
  83. // replacement相当于p和后继节点s交换后,删除s位置取代其位置的节点,参考红黑树删除过程理解
  84. if (sr != null)
  85. replacement = sr;
  86. else
  87. replacement = p;
  88. }
  89. // 删除节点的左右子树一侧为空,参考红黑树删除4种情况
  90. // 若p的左右儿子为空,则replacement为非空的一方,否则为p自己
  91. else if (pl != null)
  92. replacement = pl;
  93. else if (pr != null)
  94. replacement = pr;
  95. else
  96. replacement = p;
  97. // replacement不为p,即不为叶子节点(相当于之前所讲的红黑树中有两个NIL节点的节点)
  98. // 处理连接关系
  99. if (replacement != p) {
  100. TreeNode<K,V> pp = replacement.parent = p.parent;
  101. if (pp == null)
  102. root = replacement;
  103. else if (p == pp.left)
  104. pp.left = replacement;
  105. else
  106. pp.right = replacement;
  107. // 移除p节点
  108. p.left = p.right = p.parent = null;
  109. }
  110. // 红黑树自平衡调节,如果为红色,则不需要进行调整,否则需要自平衡调整,后面会对这个方法进行分析
  111. TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);
  112. // 后继节点无子节点,直接移除p节点即能保持平衡
  113. if (replacement == p) { // detach
  114. TreeNode<K,V> pp = p.parent;
  115. p.parent = null;
  116. if (pp != null) {
  117. if (p == pp.left)
  118. pp.left = null;
  119. else if (p == pp.right)
  120. pp.right = null;
  121. }
  122. }
  123. if (movable)
  124. // 调整root使其落在tab数组上
  125. moveRootToFront(tab, r);
  126. }

split

拆分,在HashMap.resize方法中调用 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap)。在扩容之后,红黑树和链表因为扩容的原因导致原本在一个数组元素下的Node节点分为高低位两部分(参考HashMap.resize链表部分的改变,是相同的),请查看HashMap的文章自行回顾,低位树即当前位置,高位树则在新扩容的tab上


  1. final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
  2. TreeNode<K,V> b = this;
  3. // Relink into lo and hi lists, preserving order
  4. // 分成高位树和低位树,头尾节点先声明
  5. TreeNode<K,V> loHead = null, loTail = null;
  6. TreeNode<K,V> hiHead = null, hiTail = null;
  7. int lc = 0, hc = 0;
  8. // 循环处理节点
  9. for (TreeNode<K,V> e = b, next; e != null; e = next) {
  10. next = (TreeNode<K,V>)e.next;
  11. e.next = null;
  12. // 这里bit代表扩容的二进制位(数值是扩容前的容量大小),不明白的也请参考之前的HashMap讲解文章
  13. if ((e.hash & bit) == 0) {
  14. // 0说明在低位树,即原位置
  15. if ((e.prev = loTail) == null)
  16. // 首节点设置
  17. loHead = e;
  18. else
  19. // 链表next设置
  20. loTail.next = e;
  21. loTail = e;
  22. ++lc;
  23. }
  24. else {
  25. // 同上,主要是在高位树上处理
  26. if ((e.prev = hiTail) == null)
  27. hiHead = e;
  28. else
  29. hiTail.next = e;
  30. hiTail = e;
  31. ++hc;
  32. }
  33. }
  34. // 对高低位树进行处理,将数组节点指向树根节点或者链表首节点
  35. if (loHead != null) {
  36. if (lc <= UNTREEIFY_THRESHOLD)
  37. // 拆分之后节点小于非树化阈值,转成链表结构
  38. tab[index] = loHead.untreeify(map);
  39. else {
  40. tab[index] = loHead;
  41. // 高位树为空则表示节点全部留在了低位树,不需要进行树化操作,已经树化过了
  42. if (hiHead != null) // (else is already treeified)
  43. loHead.treeify(tab);
  44. }
  45. }
  46. if (hiHead != null) {
  47. if (hc <= UNTREEIFY_THRESHOLD)
  48. tab[index + bit] = hiHead.untreeify(map);
  49. else {
  50. // 高位所处的位置为原本位置+旧数组的大小即bit
  51. tab[index + bit] = hiHead;
  52. if (loHead != null)
  53. hiHead.treeify(tab);
  54. }
  55. }
  56. }

rotateLeft

左旋操作,参考红黑树讲解中的图来理解,自己动手画一画也能明白,右旋操作类似,不再多说


  1. static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
  2. TreeNode<K,V> p) {
  3. TreeNode<K,V> r, pp, rl;
  4. // r代表M的右子节点N,先决条件,p(M),r(N)不能为空
  5. if (p != null && (r = p.right) != null) {
  6. // r的左子节点成为p的右子节点,即B成为M的右子节点
  7. if ((rl = p.right = r.left) != null)
  8. // r的左子节点父节点指向p,即修改B父节点指向M
  9. rl.parent = p;
  10. // p的父节点成为r的父节点,即N父节点为原M的父节点
  11. if ((pp = r.parent = p.parent) == null)
  12. // r的父节点为空,则r为root节点,设置为黑色,即N父节点为空,N设置为根节点
  13. (root = r).red = false;
  14. // 原p节点是左子树,即M是左子树
  15. else if (pp.left == p)
  16. // 现调整后修改N父节点左子指向N
  17. pp.left = r;
  18. else
  19. // r的父节点的右子节点需重设,即调整后修改N父节点右子指向N
  20. pp.right = r;
  21. // p和r的位置关系修改,即M与N的关系重设
  22. r.left = p;
  23. p.parent = r;
  24. }
  25. return root;
  26. }

balanceInsertion

插入节点之后进行平衡调整,x为新添加的节点,root为树的根节点,返回根节点。


  1. static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
  2. TreeNode<K,V> x) {
  3. // 根据红黑树属性插入的节点默认设置为红色
  4. x.red = true;
  5. // 通过循环进行调整,从叶子到根节点进行局部调整
  6. for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
  7. // x的父节点为空,说明x节点为根节点,将颜色置为黑即可
  8. // 符合插入节点第一种情况
  9. if ((xp = x.parent) == null) {
  10. x.red = false;
  11. return x;
  12. }
  13. // x父节点为黑色或者x的祖父节点为空
  14. // 符合插入节点第二种情况
  15. else if (!xp.red || (xpp = xp.parent) == null)
  16. return root;
  17. // 下面情况中x的颜色都为红色,因为上边已经判断过黑色
  18. // x的父节点为x祖父节点的左儿子,插入情况下三四种情况需要区分左还是右
  19. if (xp == (xppl = xpp.left)) {
  20. // x祖父右儿子,即x的叔叔不为空,且为红色
  21. // 符合插入节点第三种情况
  22. if ((xppr = xpp.right) != null && xppr.red) {
  23. // x的叔叔修改为黑色
  24. xppr.red = false;
  25. // x的父节点修改为黑色
  26. xp.red = false;
  27. // x的祖父节点修改为红色
  28. xpp.red = true;
  29. // x指向x的祖父节点,以祖父节点循环继续向上调整,相当于xpp是插入节点
  30. x = xpp;
  31. }
  32. else {
  33. // x的叔叔是黑色且x是右儿子,相当于在树的“内部”
  34. // 符合插入节点第四种情况的第一种状态
  35. if (x == xp.right) {
  36. // 以x父节点进行左旋
  37. root = rotateLeft(root, x = xp);
  38. xpp = (xp = x.parent) == null ? null : xp.parent;
  39. }
  40. // 这里处理第二种状态,相当于在树的“外部”
  41. if (xp != null) {
  42. // x的父节点改为黑色,x的祖父节点改为红色后对x的祖父节点进行右旋转
  43. xp.red = false;
  44. if (xpp != null) {
  45. xpp.red = true;
  46. root = rotateRight(root, xpp);
  47. }
  48. }
  49. }
  50. }
  51. // x的父节点为x祖父节点的右儿子
  52. // 下面跟上边类似,对称关系
  53. else {
  54. if (xppl != null && xppl.red) {
  55. xppl.red = false;
  56. xp.red = false;
  57. xpp.red = true;
  58. x = xpp;
  59. }
  60. else {
  61. if (x == xp.left) {
  62. root = rotateRight(root, x = xp);
  63. xpp = (xp = x.parent) == null ? null : xp.parent;
  64. }
  65. if (xp != null) {
  66. xp.red = false;
  67. if (xpp != null) {
  68. xpp.red = true;
  69. root = rotateLeft(root, xpp);
  70. }
  71. }
  72. }
  73. }
  74. }
  75. }

balanceDeletion

删除节点后自平衡操作,x是删除节点的替换节点,注意下


  1. static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,TreeNode<K,V> x) {
  2. // 循环操作,平衡局部之后继续判断调整
  3. for (TreeNode<K,V> xp, xpl, xpr;;) {
  4. // 删除节点为空或x为根节点直接返回,平衡调整完毕x=root
  5. if (x == null || x == root)
  6. return root;
  7. // 删除后x父节点为空,说明x为根节点,x置为黑色,红黑树平衡
  8. // 参考红黑树删除文章中的3种情况中的第一种情况,整棵树的根节点需要将根节点置黑
  9. else if ((xp = x.parent) == null) {
  10. // xp为空 x为根节点,x置为黑色
  11. x.red = false;
  12. return x;
  13. }
  14. // x为红色,原节点必为黑色,变色操作即可满足红黑树特性达到平衡
  15. // 参考红黑树删除文章中的3种情况中的第二种情况
  16. else if (x.red) {
  17. x.red = false;
  18. return root;
  19. }
  20. // 区分x为左子节点还是右子节点
  21. else if ((xpl = xp.left) == x) {
  22. // x的叔叔为红色
  23. // 符合3种情况中的第三种情况中的第二种情况
  24. if ((xpr = xp.right) != null && xpr.red) {
  25. // 先进行变色,然后进行左旋操作,xpr指向xp新的右子
  26. xpr.red = false;
  27. xp.red = true;
  28. root = rotateLeft(root, xp);
  29. xpr = (xp = x.parent) == null ? null : xp.right;
  30. }
  31. // x的兄弟为空
  32. if (xpr == null)
  33. // x指向x的父节点,继续以x父节点调整平衡
  34. x = xp;
  35. else {
  36. TreeNode<K,V> sl = xpr.left, sr = xpr.right;
  37. // 符合3种情况中的第三种情况中的第三种情况
  38. if ((sr == null || !sr.red) &&
  39. (sl == null || !sl.red)) {
  40. // sr,sl两个兄弟都是黑色,x的兄弟设置为红色,x指向x的父节点继续向上循环调整平衡
  41. xpr.red = true;
  42. x = xp;
  43. }
  44. else {
  45. // 符合3种情况中的第三种情况中的第五种情况
  46. if (sr == null || !sr.red) {
  47. // sr为空或者为黑色
  48. if (sl != null)
  49. // sl非空说明为红色,设置为黑色
  50. sl.red = false;
  51. // x的兄弟设置为红色
  52. xpr.red = true;
  53. // 右旋
  54. root = rotateRight(root, xpr);
  55. xpr = (xp = x.parent) == null ?
  56. null : xp.right;
  57. }
  58. // 符合3种情况中的第三种情况中的第六种情况
  59. if (xpr != null) {
  60. // xpr颜色设置
  61. xpr.red = (xp == null) ? false : xp.red;
  62. if ((sr = xpr.right) != null)
  63. // xpr 右孩子设置为黑色
  64. sr.red = false;
  65. }
  66. if (xp != null) {
  67. xp.red = false;
  68. // 左旋操作
  69. root = rotateLeft(root, xp);
  70. }
  71. x = root;
  72. }
  73. }
  74. }
  75. // 和上边是对称操作
  76. else { // symmetric
  77. if (xpl != null && xpl.red) {
  78. xpl.red = false;
  79. xp.red = true;
  80. root = rotateRight(root, xp);
  81. xpl = (xp = x.parent) == null ? null : xp.left;
  82. }
  83. if (xpl == null)
  84. x = xp;
  85. else {
  86. TreeNode<K,V> sl = xpl.left, sr = xpl.right;
  87. if ((sl == null || !sl.red) &&
  88. (sr == null || !sr.red)) {
  89. xpl.red = true;
  90. x = xp;
  91. }
  92. else {
  93. if (sl == null || !sl.red) {
  94. if (sr != null)
  95. sr.red = false;
  96. xpl.red = true;
  97. root = rotateLeft(root, xpl);
  98. xpl = (xp = x.parent) == null ?
  99. null : xp.left;
  100. }
  101. if (xpl != null) {
  102. xpl.red = (xp == null) ? false : xp.red;
  103. if ((sl = xpl.left) != null)
  104. sl.red = false;
  105. }
  106. if (xp != null) {
  107. xp.red = false;
  108. root = rotateRight(root, xp);
  109. }
  110. x = root;
  111. }
  112. }
  113. }
  114. }
  115. }

checkInvariants

对整棵树进行红黑树一致性的检查 目前仅在检查root是否落在table上时调用,满足红黑树的特性以及节点指向的正确性


  1. static <K,V> boolean checkInvariants(TreeNode<K,V> t) {
  2. TreeNode<K,V> tp = t.parent, tl = t.left, tr = t.right,
  3. tb = t.prev, tn = (TreeNode<K,V>)t.next;
  4. // t的前一个节点的后一个节点应为t
  5. if (tb != null && tb.next != t)
  6. return false;
  7. // tn的后一个节点的前一个节点应为t
  8. if (tn != null && tn.prev != t)
  9. return false;
  10. // t的父节点的左子节点或右子节点应为t
  11. if (tp != null && t != tp.left && t != tp.right)
  12. return false;
  13. // t的左子节点的父节点应为t并且 t的左子节点hash值小于t的hash值
  14. if (tl != null && (tl.parent != t || tl.hash > t.hash))
  15. return false;
  16. // t的右子节点的父节点应为t并且 t的右子节点hash值大于t的hash值
  17. if (tr != null && (tr.parent != t || tr.hash < t.hash))
  18. return false;
  19. // t和t的子节点不能同时是红色,红黑树特性
  20. if (t.red && tl != null && tl.red && tr != null && tr.red)
  21. return false;
  22. // 左子节点递归检查
  23. if (tl != null && !checkInvariants(tl))
  24. return false;
  25. // 右子节点递归检查
  26. if (tr != null && !checkInvariants(tr))
  27. return false;
  28. return true;
  29. }

总结

至此,关于TreeNode的代码讲解部分已经完成,类似的源码TreeMap等使用红黑树结构的类基本操作都是类似源码,可以自行查看,重要的部分在于插入和删除是如何做到的,在之后如何进行自平衡操作的,希望对各位读者有所帮助

JDK源码那些事儿之HashMap.TreeNode的更多相关文章

  1. JDK源码那些事儿之我眼中的HashMap

    源码部分从HashMap说起是因为笔者看了很多遍这个类的源码部分,同时感觉网上很多都是粗略的介绍,有些可能还不正确,最后只能自己看源码来验证理解,写下这篇文章一方面是为了促使自己能深入,另一方面也是给 ...

  2. JDK源码那些事儿之并发ConcurrentHashMap上篇

    前面已经说明了HashMap以及红黑树的一些基本知识,对JDK8的HashMap也有了一定的了解,本篇就开始看看并发包下的ConcurrentHashMap,说实话,还是比较复杂的,笔者在这里也不会过 ...

  3. JDK源码那些事儿之红黑树基础下篇

    说到HashMap,就一定要说到红黑树,红黑树作为一种自平衡二叉查找树,是一种用途较广的数据结构,在jdk1.8中使用红黑树提升HashMap的性能,今天就来说一说红黑树,上一讲已经给出插入平衡的调整 ...

  4. JDK源码那些事儿之浅析Thread上篇

    JAVA中多线程的操作对于初学者而言是比较难理解的,其实联想到底层操作系统时我们可能会稍微明白些,对于程序而言最终都是硬件上运行二进制指令,然而,这些又太过底层,今天来看一下JAVA中的线程,浅析JD ...

  5. JDK源码那些事儿之并发ConcurrentHashMap下篇

    上一篇文章已经就ConcurrentHashMap进行了部分说明,介绍了其中涉及的常量和变量的含义,有些部分需要结合方法源码来理解,今天这篇文章就继续讲解并发ConcurrentHashMap 前言 ...

  6. JDK源码那些事儿之常用的ArrayList

    前面已经讲解集合中的HashMap并且也对其中使用的红黑树结构做了对应的说明,这次就来看下简单一些的另一个集合类,也是日常经常使用到的ArrayList,整体来说,算是比较好理解的集合了,一起来看下 ...

  7. JDK源码阅读(4):HashMap类阅读笔记

    HashMap public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, ...

  8. JDK源码那些事儿之ConcurrentLinkedDeque

    非阻塞队列ConcurrentLinkedQueue我们已经了解过了,既然是Queue,那么是否有其双端队列实现呢?答案是肯定的,今天就继续说一说非阻塞双端队列实现ConcurrentLinkedDe ...

  9. JDK源码那些事儿之ConcurrentLinkedQueue

    阻塞队列的实现前面已经讲解完毕,今天我们继续了解源码中非阻塞队列的实现,接下来就看一看ConcurrentLinkedQueue非阻塞队列是怎么完成操作的 前言 JDK版本号:1.8.0_171 Co ...

随机推荐

  1. 用ExtentReports美化你的测试报告

    前言 在实际的自动化测试工作中经常会用到一些报告生成工具大概分为两类,一类是测试框架自带的报告生成工具如:JUnit+Ant.TestNG:另一类就是专用报告工具如ReportNG等.这些报告要么在U ...

  2. 登陆Linux服务器时触发邮件提醒

    目前,客户只能在发现数据或者虚拟机被恶意侵入或者用户的误操作导致了数据的丢失之后,采取善后的手段,但是并没法做到提前的预警.那么通过 PAM 模块,就可以实现用户登录及获取root 权限时,通过邮件的 ...

  3. 将 MathType 公式转换为 Word 自带公式

    以下操作是基于Office 365以及MathType 6.9b平台.有网友留言说第四步没出现「转换为 Office Math」选项,这个我就不清楚了,难道是只有Office 365才支持? 打开Ma ...

  4. 矩阵拿宝物--Codeforces 1201D - Treasure Hunting Codeforces Round #577 (Div. 2)

    网上题解比较少,自己比较弱研究了半天(已经过了),希望对找题解的人有帮助 题目链接:https://codeforc.es/contest/1201/problem/D 题意: 给你一个矩形,起始点在 ...

  5. SAS学习笔记42 宏程序

    Autocall Macro是由SAS提供的一些实现特定功能的Macro Program,可以在代码中直接使用 其中以Q开头的相比正常的多了隐藏特殊字符的功能(称之为Macro Quoting): K ...

  6. 如何将Linux的工程原封不动地移植到Windows上面

    习惯在Linux下进行开发.但是由于工作需要,不得不与其他使用Windows的项目组同事对接,同事要求我给出可用的程序,而我只有基于makefile的传统工程. 改动到VS工程上发现一部分头文件在Wi ...

  7. 为什么用JS取不到cookie的值?解决方法如下!

    注意:cookie是基于域名来储存的.要放到测试服务器上或者本地localhost服务器上才会生效.cookie具有不同域名下储存不可共享的特性.单纯的本地一个html页面打开是无效的. 明明在浏览中 ...

  8. mybaits实现oracle批量新增数据,回填主键

    项目有需求,百度了很久,反正他们说的方法,我都没成功,我也不知道是不是我写代码的姿势不正确,没办法只能自己想法子了 我们这个项目用到了通过Mapper,通用Mapper里通过OracleProvide ...

  9. ActivityMQ消息中间件【待完成】

    1,MQ的引入 使用场景,将耗时的通知业务交给消息中间件[业务逻辑进行解耦] 使用消息中间件的逻辑交互 2,MQ的应用场景 首先消息中间件是一个异步处理 有两个关键点:①耗时:②业务的耦合度 案例1: ...

  10. 设计模式(四)——代理模式(Proxy)

    代理模式的参与者有:一个约束.一个代理者.一个被代理者.一个调用者 代理模式的实现很简单:还是那个房子,对于开门这个操作,我更换了一个远程解锁的门,那么我就可以通过这个远程连接的服务器远程解锁,这样我 ...