HashMap 中 treeify() 和 untreeify() 方法详解

906 阅读3分钟

1.前言

treeify() : 树化,即当桶中元素数量元素大于等于8时且 hash表的容量>= 64时。

untreeify : 取消树化,即桶中元素小于等于6时。

这里详细的变量定义我在前面 java HashMap 详解 -- (默认常量和构造函数)

2.源码讲解

在正式讲解源码之前我们先看一下,TreeNode和Node的关系

图片.png

TreeNode 是 Node 的子类。

2.1 untreeify()

图片.png

这里主要作用就是,当扩容后,桶中的元素数量 <= 6时,会把它从红黑树结构转成链表结构

  • 这个方法调用它的是一个 TreeNode 对象
  • 首先定义头结点: hd, 和尾结点:tl
  • 这里其实很简单,不停地遍历 this对象(一个TreeNode 对象),如果头结点为null,就把它赋给头结点,否则把它赋给头结点的next。
  • 最终把生成的 节点hd 返回出去即可。

2.2 treeify()

这里主要作用就是 当链表长度大于等于8时,且hash表的容量大于64 会把链表结构 转成红黑树

final void treeify(Node<K,V>[] tab) {
    // 首先定义一个 根节点 
    TreeNode<K,V> root = null;
    // 这里调用对象通常是 Node对象,遍历Node
    for (TreeNode<K,V> x = this, next; x != null; x = next) {
        // 遍历的是链表,next 记录当前元素指向的下一个元素
        next = (TreeNode<K,V>)x.next;
        // 把x的左子树和右子树置null
        x.left = x.right = null;
        // 如果 根节点还是null 
        if (root == null) {
            // 把x的parent 置null
            x.parent = null;
            // 标记x的颜色是黑
            x.red = false;
            // 把x赋给 root
            root = x;
        }
        // 这里处理根节点不是 null 的情况
        else {
            // k:保存当前链表节点的key
            K k = x.key;
            // h:保存当前节点的hash
            int h = x.hash;
            // kc: 当前key所属的 Class
            Class<?> kc = null;
            // 从根节点开始遍历,寻找插入位置
            for (TreeNode<K,V> p = root;;) {
                // dir:记录插入的放向,-1是左侧,1是右侧,ph:记录当前红黑树节点的hash值
                int dir, ph;
                // 当前遍历的节点的 key
                K pk = p.key;
                如果 当前红黑树节点的hash值大于 当前链表节点的hash值,返回-1,表示插入当前红黑树的左边
                if ((ph = p.hash) > h)
                    dir = -1;
                // 否则 当ph<h时,表示插入右边
                else if (ph < h) 
                    dir = 1;
                // 即 ph=h,他们两的hash值相等。这里会先判断他们有没有实现 Comparable 接口,如果实现了,
                // 则 调用compareTo方法进行比较,若他们结果还是相等,则最终调用 tieBreakOrder 这个方法来比较。
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0)
                    dir = tieBreakOrder(k, pk);
                // 保存当前树的节点
                TreeNode<K,V> xp = p;
                // 这里判断dir <= 0 时 说明他会被插入到 当前红黑树节点的左边(不一定是左子节点,
                //只要在这个节点的左半边就行),同理 dir>0 表示它在当前红黑树节点的右边。
                // 如果插入的是右边就把 p.right赋给 p,插入左边就判断 p.left赋给 p ,并判断p == null? 
                // 如果p != null,则说明xp不是叶子节点,则继续循环,向下寻找节点插入。
                // 如果 p==null ,就说明xp是一个叶子节点,则把x.parent置为 xp,然后根据 dir大小判断是x置为xp的左子节点还是右子节点。
                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);
                    // 跳出当前for循环,继续插入链表中 下一个节点
                    break;
                }
            }
        }
    }
    // 把红黑树的根节点设为  其所在的数组槽 的第一个元素 2.4详解
    moveRootToFront(tab, root);
}

2.3 tieBreakOrder(k, pk)

这个方法主要是处理hash值比较和compareTo 方法比较后结果仍然相等两个对象,进行粗略比较

static int tieBreakOrder(Object a, Object b) {
    int d;
    // 先进行ab的类名进行比较,若还是相等进入
    if (a == null || b == null ||
        (d = a.getClass().getName().
         compareTo(b.getClass().getName())) == 0)
         // 通过这两个对象的hashCode进行粗略比较,即相等返回 -1
        d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
             -1 : 1);
    return d;
}

2.4 moveRootToFront(tab, root)

这个方法的主要作用就是其所在的数组槽的第一个元素是 root节点

static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
    // n:记录table的length 
    int n;
    if (root != null && tab != null && (n = tab.length) > 0) {
        // index : root最终放到数组的索引位置
        int index = (n - 1) & root.hash;
        // 取出数组 索引为index 的元素,判断它和root是否相等,相等的话就是正常的无需处理;
        // 否则就需要进行处理。
        TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
        if (root != first) {
            Node<K,V> rn;
            tab[index] = root;
            // rp:记录root节点前置节点
            TreeNode<K,V> rp = root.prev;
            // 如果 root的next节点不为null,把它赋给rn
            if ((rn = root.next) != null)
                // 把rn的前置节点设为 rp
                ((TreeNode<K,V>)rn).prev = rp;
            if (rp != null)
                // 当 rp不为null 把rp的后置节点设为rn
                rp.next = rn;
            if (first != null)
                // 当 first不为null ,就把root设为 frist的前置节点
                first.prev = root;
            // root的后置节点设为 frist
            root.next = first;
            // root的前置节点设为 null
            root.prev = null;
        }
        // 这里主要是检查当前红黑树的结构是否满足红黑树的性质。
        assert checkInvariants(root);
    }
}


static <K,V> boolean checkInvariants(TreeNode<K,V> t) {
        TreeNode<K,V> tp = t.parent, tl = t.left, tr = t.right,
            tb = t.prev, tn = (TreeNode<K,V>)t.next;
        if (tb != null && tb.next != t)
            return false;
        if (tn != null && tn.prev != t)
            return false;
        if (tp != null && t != tp.left && t != tp.right)
            return false;
        if (tl != null && (tl.parent != t || tl.hash > t.hash))
            return false;
        if (tr != null && (tr.parent != t || tr.hash < t.hash))
            return false;
        if (t.red && tl != null && tl.red && tr != null && tr.red)
            return false;
        if (tl != null && !checkInvariants(tl))
            return false;
        if (tr != null && !checkInvariants(tr))
            return false;
        return true;
    }
}