HashMap红黑树源码解读

  1. 链表转换为红黑树节点

当往hashMap中添加元素,在同一个hash槽位挂载的元素超过8个后,执行treeifyBin方法。

在treeifyBin方法中,只有当tab数组(hash槽位)的长度不小于MIN_TREEIFY_CAPACITY(默认64)时,才会将(n - 1) & hash位置处的所有节点树化。

先遍历(n-1)&hash处的链表,从头到尾,按顺序,将所有节点转换为treeNode。

  1. 树节点重新挂载

在将链表所有节点转换为TreeNode后,再执行treeify方法。

在treeify方法中,顺着头节点遍历链表设置红黑树(this是头节点)。

在遍历节点时,比较大小决定挂在红黑树的哪个节点上(先比hash,再使用Comparable接口的compareTo方法进行比较,小的挂在左边,大的挂在右边)。

  1. 红黑树平衡插入

在二叉树上新增节点后,执行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;;) {
                // x is top
                if ((xp = x.parent) == null) {
                    x.red = false;
                    return x;
                }
                // 新节点挂在root下
                else if (!xp.red || (xpp = xp.parent) == null)
                    return root;
                // 新节点挂在树左边,左子节点
                if (xp == (xppl = xpp.left)) {
                    // 新节点(1)挂在最左的左边,不需旋转,只需改变颜色
                    //       black(3)                  red(3)
                    //   red(2)    red(4)    =>   black(2)   black(4)
                    // red(1)                   red(1)
                    if ((xppr = xpp.right) != null && xppr.red) {
                        xppr.red = false;
                        xp.red = false;
                        xpp.red = true;
                        x = xpp;
                    }
                    else { // 新节点(2)挂在最左的右边
                        //       black(3)                      black(3)
                        //  red(1)         rotateLeft=>   red(2)    
                        //        red(2)              red(1) 
                        if (x == xp.right) {
                            root = rotateLeft(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        if (xp != null) {
                            //       black(3)            black(3) xpp       
                            //    red(2)      =>     black(2) xp  
                            // red(1)              red(1) x
                            xp.red = false;
                            if (xpp != null) {
                                xpp.red = true;
                                //        red(3)                 
                                //    black(2)      =>  black(2)
                                // red(1)           red(1)    red(3)
                                root = rotateRight(root, xpp);
                            }
                        }
                    }
                }
                else { // 新节点(4)挂在树最右的右边 xp != (xppl = xpp.left)
                    //       black(2)                  red(2)
                    //   red(1)    red(3)    =>   black(1)  black(3)
                    //                red(4)                    red(4)
                    if (xppl != null && xppl.red) {
                        xppl.red = false;
                        xp.red = false;
                        xpp.red = true;
                        x = xpp;
                    }
                    else {
                        // 新节点(2)挂在树最右的左边 
                        // black(1)           black(1)    
                        //        red(3)  =>       red(2)  
                        //    red(2)                    red(3)
                        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) {
                                // red(1)       
                                //    black(2)    =>    black(2)
                                //         red(3)    red(1)   red(3)
                                xpp.red = true;
                                root = rotateLeft(root, xpp);
                            }
                        }
                    }
                }
            }
        }

分析下balanceInsertion的思路:二叉树新增节点导致不符合红黑定义时,通过改变颜色和旋转(左旋或右旋)使得红黑树再次平衡,红黑树概念及旋转可参考https://blog.csdn.net/weixin_43790276/article/details/106042360

根据插入节点是挂在左子节点(xp == (xppl = xpp.left)),还是右子节点,分情况进行了操作,下面是挂在左子节点时的三种情况。

下面为挂在右子节点时的三种情况,这六种情况,覆盖了所有插入时会导致的红黑树结构破坏。

新节点挂载并重新平衡红黑树后,将root节点移动到hash槽位的最前面

  1. rotateLeft左旋方法

简单说下,为什么要旋转节点,因为往二叉树中挂载新节点时,未严格按照红黑树的规定挂载(按照规定挂载会很麻烦),所以先按二叉树的规则挂载,再通过左旋、右旋、改变颜色来使其符合红黑树规定。

什么是左旋,其实主要是为了把旋转节点与它的右子节点进行位置互换,旋转节点相对于右子节点来说,它往左边移动了,所以叫左旋;右旋类似,这样不管二叉树是怎么样的,总可以通过左旋、右旋,挪动它们的位置,再加上改变颜色,使其达到红黑树的形态。

        // root:根节点 p:当前旋转节点
        static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                              TreeNode<K,V> p) {
            // r:右子节点 pp:父父节点 rl:右左节点
            TreeNode<K,V> r, pp, rl;
            // 必须当前节点和右子节点存在才左旋
            if (p != null && (r = p.right) != null) {
                if ((rl = p.right = r.left) != null)
                    // 把右左节点挂在当前节点右边,见下面的左旋情况2
                    rl.parent = p;
                if ((pp = r.parent = p.parent) == null)
                    // 右节点挪到上方,变成根节点,见下面的左旋情况2
                    (root = r).red = false;
                else if (pp.left == p)
                    // 右子节点移到父父节点左边,见下面的左旋情况5
                    pp.left = r;
                else
                    // 右子节点移到父父节点右边,见下面的左旋情况6
                    pp.right = r;
                // 右子节点移到旋转节点上方,见下面的左旋情况1
                r.left = p;
                p.parent = r;
            }
            return root;
        }

上述左旋方法覆盖了6种二叉树左旋情况(注意二叉树有时并不能通过一次左旋就能变为红黑树,有时需要多次左旋或右旋及改变颜色),转换过程覆盖的情况画图演示如下。

左旋情况1,最简单的左旋,旋转节点p为1。

左旋情况2,旋转节点p为1。

左旋情况3,旋转节点p为1。

左旋情况4,旋转节点p为2。

左旋情况5,旋转节点p为4。

左旋情况6,旋转节点p为2。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kenick

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值