HashMap源码分析--红黑树(3)

2020-02-13  本文已影响0人  NiNko

之前几篇博客写了关于HashMap源码的分析,到现在就只剩关于红黑树的部分。红黑树是一种数据结构,它是基于二叉查找树的。红黑树很好的继承了二叉查找树快速插入删除以及查找元素的特性,又因为红黑树自平衡,所以又克服了二叉查找树在输入线性数据时退化为链表的缺点,因此被广泛使用。


java version 1.8


1. 红黑树的特性

红黑树,顾名思义,有一个特征就是树的节点要么是红色,要么是黑色。而其本身又是自平衡的二叉查找树,除了满足二叉查找树的特征之外,还应满足以下特征,来保证自平衡性。

红黑树在插入节点时,通过这些特性来保持自身的平衡性。而保持平衡性的操作主要有三个,改变节点颜色,左旋,右旋。

2. 链表转红黑树

惯例,先看源码

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;
}

TreeNode是HashMap的内部类,继承自LinkedHashMap.Entry,而LinkedHashMap.Entry继承自HashMap的Node类。它的几个属性parent指向红黑树中的父节点,left和right分别指向左右孩子节点,prev指向原链表中的上一个节点。

    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);
        }
    }

当hashmap数组中某位置链表的长度大于等于7时,该链表就被转化为红黑树,使用treeifyBin方法。
这个方法的作用是将链表的每个节点都转化为红黑树节点,并将单链表转化为双链表。之后使用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);
    }

treeify方法遍历双链表的每个节点,将每个节点插入到二叉查找树中,每插入一个节点,调用一次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是叔叔节点,父节点和叔叔节点均为红色,给节点变色,然后将其祖父节点置为当前节点
                        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);
                            }
                        }
                    }
                }
            }
        }

balanceInsertion方法参数中root是红黑树的根节点,x是当前插入的节点。从源码中可以看出,插入的节点默认置为红色。

3. 红黑树删除节点

惯例,先看代码

    final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                                  boolean movable) {
            int n;
            if (tab == null || (n = tab.length) == 0)
                // 判空
                return;
            int index = (n - 1) & hash;
            // first是红黑树的第一个节点,succ是当前节点在hashmap链表中的后一个节点
            // pred是在hashmap链表中的前一个节点
            TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
            TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
            if (pred == null)
                // 前一个结点为空,说明当前节点是根节点
                tab[index] = first = succ;
            else
                // 将前一个节点的后继指向后一个节点,也即删除链表中的这个节点
                pred.next = succ;
            if (succ != null)
                // 节点后继的前驱指向前一个节点,双链表删除操作完成
                succ.prev = pred;
            if (first == null)
                return;
            if (root.parent != null)
                root = root.root();
            if (root == null
                || (movable
                    && (root.right == null
                        || (rl = root.left) == null
                        || rl.left == null))) {
                tab[index] = first.untreeify(map);  // too small
                return;
            }
            TreeNode<K,V> p = this, pl = left, pr = right, replacement;
            if (pl != null && pr != null) {// 左右子树均不为空
                TreeNode<K,V> s = pr, sl;
                while ((sl = s.left) != null) // 右子树的最左节点
                    s = sl;
                boolean c = s.red; s.red = p.red; p.red = c; // swap colors
                TreeNode<K,V> sr = s.right;
                TreeNode<K,V> pp = p.parent;
                if (s == pr) { // p was s's direct parent
                    p.parent = s;
                    s.right = p;
                }
                else {
                    TreeNode<K,V> sp = s.parent;
                    if ((p.parent = sp) != null) {
                        if (s == sp.left)
                            sp.left = p;
                        else
                            sp.right = p;
                    }
                    if ((s.right = pr) != null)
                        pr.parent = s;
                }
                p.left = null;
                if ((p.right = sr) != null)
                    sr.parent = p;
                if ((s.left = pl) != null)
                    pl.parent = s;
                if ((s.parent = pp) == null)
                    root = s;
                else if (p == pp.left)
                    pp.left = s;
                else
                    pp.right = s;
                if (sr != null)
                    replacement = sr;
                else
                    replacement = p;
            }
            else if (pl != null)
                replacement = pl;
            else if (pr != null)
                replacement = pr;
            else
                replacement = p;
            //p是待删除节点
            if (replacement != p) {
                TreeNode<K,V> pp = replacement.parent = p.parent;
                if (pp == null)
                    (root = replacement).red = false;
                else if (p == pp.left)
                    pp.left = replacement;
                else
                    pp.right = replacement;
                p.left = p.right = p.parent = null;
            }
            // 删除节点为红色,红黑树平衡违背破坏
            TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);
            // 删除节点p
            if (replacement == p) {  // detach
                TreeNode<K,V> pp = p.parent;
                p.parent = null;
                if (pp != null) {
                    if (p == pp.left)
                        pp.left = null;
                    else if (p == pp.right)
                        pp.right = null;
                }
            }
            if (movable)
                moveRootToFront(tab, r);
        }

这个方法是删除二叉查找树中的节点,平衡红黑树在balanceDeletion方法中。
就先到这里,具体逻辑下一次再分析。


学疏才浅,有不当之处,望指出。

上一篇 下一篇

猜你喜欢

热点阅读