JDK源码分析之HashMap(二)

221 阅读4分钟

一、前言

在上一章说了一些HashMap的主要方法,以及构造函数配置的说明,这一章就说说JDK1.8新引进的红黑树。

二、什么是红黑树

红黑树其实就是一种二叉排序树(外面都说是平衡二叉树,但是有一定的情况下不是,就是它左右两个子数的高度差有可能超过一,具体的涉及到数据结构就不细说了。),它的平衡就是说从根结点到各个子节点的路径经过的黑色结点都是相同的。 具体它还有几个规则,根据下图细说:

  1. 结点只有红色和黑色
  2. 根结点和叶子节点都必须是黑色
  3. 从根结点到任意一个叶子结点(NIL结点)经过黑色的节点数都是相同的
  4. 每个红色结点下面必有两个黑色的结点

三、红黑树的好处

它降低了平衡的要求,可以降低旋转的次数(左旋和右旋),可以提高编辑数据的效率。而且正是因为它的特性,不会有一条路径长度是别的长度的两倍,这样子对于查找的效率可以得到保证。

四、HashMap中红黑树的具体实现

final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        //小于8则不变
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        //大于8了,转成树形
        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);
        }
    }

注意: 当前方法直接将数据结构转成树形,转成红黑树是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;;) {
                        //定义位置和位置hash属性
                        int dir, ph;
                        K pk = p.key;
                        //比较hash值
                        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);
        }
//树形结构新增数据方法
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;
            //判断结点的hash值和要添加的hash值
            for (TreeNode<K,V> p = root;;) {
                int dir, ph; K pk;
                //大于放左边
                if ((ph = p.hash) > h)
                    dir = -1;
                //小于放右边
                else if (ph < h)
                    dir = 1;
                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                    return p;
                //解决hash相同发生冲突的问题
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0) {
                    //在左右结点中寻找是否存在equal相等的值
                    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;
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    Node<K,V> xpn = xp.next;
                    //创建新节点
                    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;
                    //balanceInsertion()旋转操作,moveRootToFront()保持平衡确保根节点移至最前
                    moveRootToFront(tab, balanceInsertion(root, x));
                    return null;
                }
            }
        }

balanceInsertion()较为复杂,需要大量的图和说明,推荐一个地址:blog.csdn.net/weixin_4234…

五、总结

HashMap中红黑树的整体实现和数据结构以及旋转操作有较大的联系,建议可以先在书籍上熟悉左旋和右旋的操作再去看实际的源码。