源码分析之HashMap的红黑树实现

JDK1.8中,HashMap底层是用数组Node<K,V>数组存储,数组中每个元素用链表存储元素,当元素超过8个时,将链表转化成红黑树存储。

红黑树

红黑树本质上是平衡查找二叉树,用于存储有序数据,相对于链表数据查找来说,链表的时间复杂度O(n),红黑树的时间复杂度O(lgn)

《源码分析之HashMap的红黑树实现》

红黑树需要满足一下五种特性:

  • 每个节点是红色或者黑色
  • 根节点是黑色
  • 每一个空叶子节点必须是黑色
  • 红色节点的子节点必须是黑色
  • 节点中到达任意子节点包含相同数组的黑节点

当新增节点或者减少节点,红黑树会通过左旋或者右旋操作来调整树结构,使其满足以上特性。

TreeNode 类

HashMap中红黑树是通过TreeNode类构造的。TreeNodeHashMap的静态内部类,继承与LinkedHashMap.Entry<K,V>


 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(int hash, K key, V val, Node<K,V> next) {
            super(hash, key, val, next);
        }
        ...
}        

treeifyBin 方法

HashMapput方法时候,但数组中某个位置的链表长度大于8时,会调用treeifyBin方法将链表转化为红黑树。

    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)
            // 数组大小小于64的,调用resize将数组大小扩容至2倍大小
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            // 遍历链表,将链表元素转化成TreeNode链
            do {
                // 调用replacementTreeNode构造TreeNode
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    // TreeNode链为空,将元素设置为hd的首个节点
                    hd = p;
                else {
                    // TreeNode链不为空,向TreeNode链后面添加元素
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                // TreeNode链表转化为红黑树
                hd.treeify(tab);
        }
    }

构成红黑树–treeify 方法

treeify方法是TreeNode类的方法,作用是将Treenode链转化成红黑树。

        final void treeify(Node<K,V>[] tab) {
            TreeNode<K,V> root = null;
            for (TreeNode<K,V> x = this, next; x != null; x = next) {
                // 遍历TreeNode链
                next = (TreeNode<K,V>)x.next;
                x.left = x.right = null;
                if (root == null) {
                    // 设置root节点
                    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;
                        // hashMap元素的hash值用来表示红黑树中节点数值大小
                        if ((ph = p.hash) > h)
                            // 当前节点值小于根节点,dir = -1
                            dir = -1;
                        else if (ph < h)
                            // 当前节点值大于根节点, dir = 1
                            dir = 1;
                        else if ((kc == null &&
                                  (kc = comparableClassFor(k)) == null) ||
                                 (dir = compareComparables(kc, k, pk)) == 0)
                            // 当前节点的值等于根节点值。
                            // 如果当前节点实现Comparable接口,调用compareTo比较大小并赋值dir
                            // 如果当前节点没有实现Comparable接口,compareTo结果等于0,则调用tieBreakOrder继续比较大小
                            // tieBreakOrder本质是通过比较k与pk的hashcode
                            dir = tieBreakOrder(k, pk);
                        // 当前“根节点”赋值给xp
                        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;
                        }
                    }
                }
            }
            // 确保红黑树根节点是数组中该index的第一个节点
            moveRootToFront(tab, root);
        }

新增元素后平衡红黑树–balanceInsertion方法

当红黑树中新增节点的时候需要调用balanceInsertion方法来保证红黑树的特性。

  static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                                    TreeNode<K,V> x) {
            // 新增节点默认是红色
            x.red = true;
            // xp父节点 xpp祖父节点 xppl祖父左节点 xppr 祖父右节点
            for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
                if ((xp = x.parent) == null) {
                    // x的父节点为空,x应为根节点,应为黑色
                    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) {
                            // x为父节点右节点,则要进行左旋操作
                            root = rotateLeft(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        // 经过左旋x为左节点
                        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;
                        }
                        // x为右节点
                        if (xp != null) {
                            xp.red = false;
                            if (xpp != null) {
                                xpp.red = true;
                                // 以祖父节点为支点左旋
                                root = rotateLeft(root, xpp);
                            }
                        }
                    }
                }
            }
        }

向红黑树添加元素 — 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;;) {    
                // 从root节点开始遍历
                int dir, ph; K pk;
                // 通过比较hash大小确定添加元素的位置
                if ((ph = p.hash) > h)
                    dir = -1;
                else if (ph < h)
                    dir = 1;
                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                    // key相同直接返回
                    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;
                // 根据dir大小添加元素
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    Node<K,V> xpn = xp.next;
                    // 构建新的treeNode节点
                    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;
                    // 平衡红黑树并保证root是index处首节点
                    moveRootToFront(tab, balanceInsertion(root, x));
                    return null;
                }
            }
        }

TreeNode类还有很多方法如删除红黑树节点方法removeTreeNode,删除后平衡二叉树方法balanceDeletion,查找红黑树节点方法getTreeNode…后续有精力再来分析。

    原文作者:特立独行的猪手
    原文地址: https://www.jianshu.com/p/5b157d4be1ad
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞