红黑树在hashmap中的使用

103 阅读3分钟

1、什么是红黑树?

红黑树是每个结点都带有颜色属性的二叉查找树,颜色或红色或黑色。 在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

性质1. 结点是红色或黑色。

性质2. 根结点是黑色。

性质3. 所有叶子都是黑色。(叶子是NIL结点)

性质4. 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)

性质5. 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。

image.png

3、主要方法

treeifyBin方法 ——列表进行树化

/*
 * Replaces all linked nodes in bin at index for given hash unless table is too small, in which case resizes instead.
 */
final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
     //判断 数组是空的 或者  数量小于64  那就去扩容吧 别来树化了
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
     //以防万一 在确定一下 你传的这个位置上是不是空的  顺便给e赋值
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        TreeNode<K,V> hd = null, tl = null;
        do {
            // 将每个节点包装成TreeNode。
            TreeNode<K,V> p = replacementTreeNode(e, null);
            //下面这一步 就是熟悉的 将单链表 变成双向链表
            //然后交给treeify进行树化了
            if (tl == null)
                hd = p;
            else {
                // 将所有TreeNode连接在一起此时只是链表结构。
                p.prev = tl;
                tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);
        //执行完上面的循环  则这个列表是双向的列表
        //下面这个判断 在进行 判断 那个位置 是否为空
        if ((tab[index] = hd) != null)
             // 对TreeNode链表进行树化。
            hd.treeify(tab);
    }
}
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);
    }
}

treeify方法——将节点变成树

final void treeify(Node<K,V>[] tab) {
    TreeNode<K,V> root = null;
     //利用当前穿过来的这个 this 进行遍历  前面说了 low 和hig 只用到了 prev 和next 更像是列表
            //所以这个方法是让他们彻底变成真正得树
    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;
        }
         //root不为空,根节点已经存在了,处理剩下对象
        else {
            K k = x.key;
            int h = x.hash;
            Class<?> kc = null;
            // 此时红黑树已经有了根节点,上面获取了当前加入红黑树的项的key和hash值进入核心循环。
            // 这里从root开始,是以一个自顶向下的方式遍历添加。
            // for循环没有控制条件,由代码内break跳出循环。
            for (TreeNode<K,V> p = root;;) {
                // dir:directory,比较添加项与当前树中访问节点的hash值判断加入项的路径,-1为左子树,+1为右子树。
                int dir, ph;  // ph:parent hash。
                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; // xp:x parent
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    x.parent = xp;
                    // 如果xp的hash值大于x的hash值,将x添加在xp的左边。
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                     // 维护添加后红黑树的红黑结构。
                    root = balanceInsertion(root, x);
                    // 跳出循环当前链表中的项成功的添加到了红黑树中
                    break;
                }
            }
        }
    }
     //再次确定根节点
    moveRootToFront(tab, root);
}

split方法——扩容时将树进行拆分

final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
            //tab是newtable  不是oldtable
            //index 时 咱要拆分的那个树所在 坐标
            //bit  是 oldcap容量
            TreeNode<K,V> b = this;
            // Relink into lo and hi lists, preserving order
            TreeNode<K,V> loHead = null, loTail = null;
            TreeNode<K,V> hiHead = null, hiTail = null;
            int lc = 0, hc = 0;  //这是用来记录两颗树的  长度的 要是 太小了 就要进行变换  将其变成链表
            for (TreeNode<K,V> e = b, next; e != null; e = next) {
                next = (TreeNode<K,V>)e.next;
                e.next = null;
                //这一步 也是和 前面的列表操作类似 也是 将 里面的树 进行区分位low 和hig
                if ((e.hash & bit) == 0) {
                    if ((e.prev = loTail) == null)
                        loHead = e;
                    else
                        loTail.next = e;
                    loTail = e;
                    ++lc;
                }
                else {
                    if ((e.prev = hiTail) == null)
                        hiHead = e;
                    else
                        hiTail.next = e;
                    hiTail = e;
                    ++hc;
                }
            }
            //执行完这操作 就分成了两颗树 说是树但是 low和hig 其实只是在用  pre 和nex 更像是 列表
            //然后 看看low这棵树 元素熟路是否小于等于UNTREEIFY_THRESHOLD(默认6)
            if (loHead != null) {
                if (lc <= UNTREEIFY_THRESHOLD)
                    //如果数量太少则进行链化
                    tab[index] = loHead.untreeify(map);
                else {
                    //没有则满足要求 进行位置的移动即可
                    tab[index] = loHead;
                    if (hiHead != null) // (else is already treeified)
                        //将这个low树进行重新树化   
                        // 为什么是树了还要树化?  上面说了 他们只用到了prev和next 完全不是树 所以需要进行树化 之所以叫他们说是因为他们是treenode类
                        //至于为什么 需要判断hiHead != null 才能进行操作 是为了提高效率
                        //比如你想一下你搞半天  hig那个树 啥也没有 不就是证明 都在low上面 那还需要重新树化什么 直接搞过去就行
                        loHead.treeify(tab);
                }
            }
            //与上面的原理一致
            if (hiHead != null) {
                if (hc <= UNTREEIFY_THRESHOLD)
                    tab[index + bit] = hiHead.untreeify(map);
                else {
                    tab[index + bit] = hiHead;
                    if (loHead != null)
                        hiHead.treeify(tab);
                }
            }
        }
​
​

untreeify方法——(将节点变成链表)

   final Node<K,V> untreeify(HashMap<K,V> map) {
            Node<K,V> hd = null, tl = null;
            //从当前传过来的节点开始遍历
            for (Node<K,V> q = this; q != null; q = q.next) {
                Node<K,V> p = map.replacementNode(q, null);
                if (tl == null)
                    hd = p;
                else
                    tl.next = p;
                tl = p;
            }
            return hd;
        }
​