HashMap之TreeNode

HashMap之TreeNode

##简述
在分析HashMap之前先说一下内部类TreeNode。TreeNode类是一颗红黑树的各种操作。当然TreeNode不只是简单的红黑树操作,还有与HashMap业务相关的代码
先看一下类的继承关系
HashMap之TreeNode

Entry是一个接口,主要有一些让子类去实现的get、set方法
Node是一个单向链表
最后就是TreeNode红黑树了

先看一下简单的Node单向链表,然后再看复杂一点的TreeNode

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    public final String toString() { return key + "=" + value; }

    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            if (Objects.equals(key, e.getKey()) &&
                Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}

很简单,一个Node相当于链表的一个节点,next属性对应着下一个节点

TreeNode
TreeNode也是,一个TreeNode代表红黑树的一个节点
红黑树是在二叉查找树基础上,对每一个节点添加一个颜色属性形成的,同时整个红黑二叉树需要同时满足一下五条性质

  1. 每个节点或是红色,或是黑色的
  2. 跟节点是黑色的
  3. 每个叶节点(NIL)是黑色的
  4. 如果一个节点是红色的,则它的两个子节点都是黑色的
  5. 对每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数码的黑节点
    如图
    HashMap之TreeNode
    叶节点(NIL)不包含任何数据,只是一个标记,没有实际意义

下面通过红黑树的插入、删除、查找、左旋、右旋进一步了解TreeNode

先看一下构造

TreeNode(int hash, K key, V val, Node<K,V> next) {
    super(hash, key, val, next);
}

可以看到调用了父类的构造方法,也就是说,TreeNode是红黑树的同时也是一个单项链表

##旋转
先说一下左旋和右旋操作
添加和删除操作肯能会违反红黑树的的性质,而保持红黑树的性质是通过旋转来操作的
HashMap之TreeNode
如添加了一个红节点N就违反了性质4,在通过2个动图看一下是怎么旋转的

左旋
HashMap之TreeNode
右旋
HashMap之TreeNode
以上面的静态图来说明右旋操作,左旋一样,只是操作是相反的
上图添加了节点N违反性质4,右旋G节点:

  • 使P成为根节点并变成黑色满足性质2
  • G成为P的右节点
  • P的右节点成为G的左节点
static <K, V> TreeNode<K, V> rotateRight(TreeNode<K, V> root,
                                         TreeNode<K, V> p) {
    TreeNode<K, V> l, pp, lr;
    if (p != null && (l = p.left) != null) {
        if ((lr = p.left = l.right) != null)
            lr.parent = p;
        if ((pp = l.parent = p.parent) == null)
            (root = l).red = false;
        else if (pp.right == p)
            pp.right = l;
        else
            pp.left = l;
        l.right = p;
        p.parent = l;
    }
    return root;
} 

代码虽然不多,但是有点乱,我们一点一点的分析。我们假设传入的参数roo是G节点,p也是G节点
参数root是根节点,p是被旋转的节点
变量l是p的左节点、pp是p的父节点、lr是l右节点

第4行判断p不等于null并把左节点赋值给l,如果l为null的话就不能右旋了
第5行是完成上面性质c,并把值赋值给lr
第7行把p的父节点赋值给l的父节点,并把值赋值给pp,因为我们传入的参数G没有父节点,所以pp为null,为了满足红黑树的性质2,把颜色设置成黑色
第9行和第11行,是判断p是左节点还是右节点,如果是右节点则吧父节点的右节点设置成l,反之亦然
第13行设置l的右节点为p,也就是P的右节点设置成G
第14行设置p的父节点为l,也就是设置G的父节点为P

##查找
从当前节点查找指定节点,根据参数h判断是从左边还是右边查找

   final TreeNode<K, V> find(int h, Object k, Class<?> kc) {
    TreeNode<K, V> p = this;
    do {
        int ph, dir;K pk;
        TreeNode<K, V> pl = p.left, pr = p.right, q;
        if ((ph = p.hash) > h)
            p = pl;
        else if (ph < h)
            p = pr;
        else if ((pk = p.key) == k || (k != null && k.equals(pk)))
            return p;
        else if (pl == null)
            p = pr;
        else if (pr == null)
            p = pl;
        else if ((kc != null ||
                (kc = comparableClassFor(k)) != null) &&
                (dir = compareComparables(kc, k, pk)) != 0)
            p = (dir < 0) ? pl : pr;
        else if ((q = pr.find(h, k, kc)) != null)
            return q;
        else
            p = pl;
    } while (p != null);
    return null;
}

参数h是TreeNode的hash,而hash就是保持平衡二叉树的字段,通过它进行比较,小的放左边,大的放右边
参数k是要查找的节点的key
参数kc是要查找节点实现的key的运行时类型

第2行p赋值当前节点,从当前节点开始查找
变量ph是当前节点的hash
变量dir是Comparable接口的比较结果
变量pl当前节点的左节点
变量pr当前节点的右节点
变量q是右节点查找时候的返回值,如果找到了返回找到的节点

分3点来说find方法

1.find方法根据当前节点的hash与参数的h比较,如果大于pl赋值给p,如果小于pr赋值给p,相等的情况通过当前节点的key与参数的key比较,不相等在通过equals方法比较。

2.接下来在判断如果pl等于null把br赋值给p,pr等于null的话吧pl赋值给p,都不等于空的话在根据key实现的Comparable比较。comparableClassFor方法是获取k的运行时类型,compareComparables方法先判断,key与运行时kc是同类型,在通过调用k和kc实现的Comparable接口的compareTo进行比较

static int compareComparables(Class<?> kc, Object k, Object x) {
    return (x == null || x.getClass() != kc ? 0 :
            ((Comparable)k).compareTo(x));
}

3.以上这些条件如果都不满足,那右节点通过递归比较找到则返回,找不到,把左节点赋值给p,进入下次循环在比较

##插入
从根节点遍历,然后通过hash比较,找到合适的位置插入
插入的代码不多,但描述起来有点费劲,所以我分成3段来说明
1.

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;                                          

参数map是用来创建新的节点,通过条用map的newTreeNode
参数tab
参数h是节点的hash,find方法说过hash的作用,不再敖述
参数k是插入节点的key
参数v是插入节点的value
变量kc与find的kc作用一样
变量 searched用来判断只搜索一次的标记
变量root是根节点,如果parent不为空会通过root方法来获取

final TreeNode<K, V> root() {
    for (TreeNode<K, V> r = this, p; ; ) {
        if ((p = r.parent) == null)
            return r;
        r = p;
    }
}

就剩下一个for循环了

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

变量dir,如果小于等于0,节点插入到左边,否则插入到右边
变量ph,当前遍历节点的hash
变量pk,当前节点的key

第1-8行,根据hash判断节点放在一边,如果hash值相等,并且key相等,不插入,直接返回节点
9-21行,在用key实现的接口去比较如果还是相等,如果是第一次搜索,那么分别通过左节点和右节点搜索,如果搜索到了也不插入,直接返回。搜索不到会调用tieBreakOrder方法通过key的hasCode比较

static int tieBreakOrder(Object a, Object b) {
    int d;
    if (a == null || b == null ||
        (d = a.getClass().getName().
         compareTo(b.getClass().getName())) == 0)
        d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
             -1 : 1);
    return d;
}

通过条用System.identityHashCode进行比较
3.
插入的最后一段代码,如果上面找到了合适的位置那么这里就插入

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;
    moveRootToFront(tab, balanceInsertion(root, x));
    return null;
}

变量xp是当前遍历的节点
2行根据dir判断是左节点还是右节点,然后为null就插入
3行,上面说TreeNode是红黑树,但它的父类是个链表,所以可以通过xp.next获得下一个节点
4行通过map.newTreeNode创建一个新节点,稍后查看是怎么创建一个新节点的
5-8行,判断节点x放在左还是右
9-12行,设置链表的相关属性

TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
    return new TreeNode<>(hash, key, value, next);
}

没什么特别的就是创建一个TreeNode

13行,插入一个节点肯能会破坏红黑树的性质,所以调用balanceInsertion方法保持红黑树的性质。在说方法moveRootToFront之前得说一下参数tab,tab是HashMap存节点的数组,每一个节点都是一颗红黑树,下一章在细说这个tab,这里只知道它是一个红黑树的数组。
balanceInsertion方法保持红黑树性质,根节点可能会被换掉,上面说过红黑树同时还是一个链表,根节点换了,那么链表头也就换了,所以moveRootToFront所做的是更正链表头。先看balanceInsertion方法,之后再看moveRootToFront方法

有3种情况会打乱红黑树的性质
1.插入节点的父节点是红色,父节点的邻居称叔节点,同时叔节点也是红色的情况。通过把父节点和爷爷节点变色来满足红黑树的性质,如图
HashMap之TreeNode
2.叔节点也是黑色,父节点是红色,并且节点是一个右子节点。通过左旋父节点变成情况3,如图
HashMap之TreeNode
3.叔节点也是黑色,父节点是红色,并且节点是一个左子节点。通过右旋父节点使红黑树满足条件
HashMap之TreeNode
代码也是针对上面的3中情况来处理的

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

第3行,让插入的节点默认为红色。然后就是一个for循环了,直到满足红黑树的性质后返回
第4行,变量xp是x的父节点,xpp是x的爷爷节点,xppl是爷爷节点的左节点,xppr是爷爷节点的右节点。
第5-8行判断如果没有父节点,那么当前节点就是跟节点,然后返回
第9-10行判断如果父节点是黑色,或者爷爷节点为空返回
第11-31与32-52行是相反的操作,上面说的打乱红黑树的3种情况,第2、3种在32-52行也会相应的反过来,所以这里只说明11-31行,余下代码就明白了
第11行判断,如果父节点是爷爷节点的左节点
第12-17行判断就是上面说的第一种情况,父节点和叔节点都是红色,然后把爷爷的右节点变为黑色,父节点变为黑色,爷爷节点变为红色,把爷爷节点赋值给x,继续循环,直到满足条件
18-32行的else包含了上述的2、3两种情况,这个就不做过多的解释了,上述描述已经解释过这两种情况

好了,现在在来看moveRootToFront方法

static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
    int n;
    if (root != null && tab != null && (n = tab.length) > 0) {
        int index = (n - 1) & root.hash;
        TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
        if (root != first) {
            Node<K,V> rn;
            tab[index] = root;
            TreeNode<K,V> rp = root.prev;
            if ((rn = root.next) != null)
                ((TreeNode<K,V>)rn).prev = rp;
            if (rp != null)
                rp.next = rn;
            if (first != null)
                first.prev = root;
            root.next = first;
            root.prev = null;
        }
        assert checkInvariants(root);
    }
}

上面解释了tab参数,第5行取出经过balanceInsertion摧残之后的根节点,第6行判断如果与摧残之前的节点不同,通过7-17的代码调整链表顺序

##删除

删除比较复杂一些,大流程和插入一样,先删除节点,然后可能会不满足红黑树的性质,在进行调整
先说删除节点方法removeTreeNode,之后再说调整节点方法balanceDeletion,在删除完就会被调用

removeTreeNode方法是删除调用当前方法的节点,删除节点的时候分为3种情况
1.如果没有子节点,那么直接删除,并修改一下父节点
2.如果只有一个孩子节点,将孩子节点提上来,并修改父节点,如图(a)和(b)
3.如果有两个孩子节点,从右节点找到没有左节点的位置,并与要删除的节点互换。分两种情况,一种是图(c),z是被删除的节点,y没有左孩子与z互换,y是z的右孩子这种情况比较简单。另一种情况图(d)当z与y互换时,涉及到l、r、x节点,y移到z的位置,x成为r的左节点
HashMap之TreeNode
图片截自《算法导论》

代码处理这三种情况时,是先交换z与y的位置和颜色,然后在删除
代码过程,我们来分段阅读

 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;
    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 || root.right == null ||
        (rl = root.left) == null || rl.left == null) {
        tab[index] = first.untreeify(map);  // too small
        return;
    }

3-5行,非空判断,并把tab的长度赋值给n,tab与插入方法的参数tab相同
6行找到当前节点在tab的下标
7-8行,变量first为当前节点,变量root为跟节点默认值为first,变量rl根节点的左节点,变量succ当前链表的下一个节点,变量prev当前链表的上一个节点
9-14行,从链表中删除当前节点
17-18行,把根节点赋值到root变量
19-23行,判断如果当前没有很多节点,就不用红黑树,转换成链表返回

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) // find successor
        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;
}

变量p当前节点,变量pl当前节点的左节点,pr当前节点的右节点,变量replacement用来替换被删除节点的位置
2行,是情况1,左右节点都不为null的情况
3行,变量s是当前节点的右节点,然后通过查找没有左节点的节点,变量sl,s的左节点
4-5行,找到s节点
6行,交换s和p也就是被删除节点的颜色
7-8行,变量sr是s的右节点,变量pp是当前节点的父节点
9-12行,是判断当前节点的右节点没有左节点,s节点和当前节点互换位置
13-23行,否则,还是s节点和当前节点互换位置,这段代码和9-12要做的事情是一样的,只是涉及到了s的父节点的属性要赋值
24-26行,如果s有右节点,把p设置成sr的父节点
27-28行,把p的左节点交接给s
29-34行,把p的父节点交接给s
35-39行,如果sr不为null替换p的节点就是sr,否则为p

处理完左右节点都不为空的情况,接下来该处理,左节点不为null右节点为null、右节点不为null左节点为null、左右都为null

else if (pl != null)
    replacement = pl;
else if (pr != null)
    replacement = pr;
else
    replacement = p;
  if (replacement != p) {
    TreeNode<K,V> pp = replacement.parent = p.parent;
    if (pp == null)
        root = replacement;
    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);

1-10行,上面把要删除的节点设置成了replacement的parent,这里把要删除的移除掉,并把移除掉节点的相关属性设置为null
12行,就是调用矫正红黑树性质的方法balanceDeletion的地方,我们先把删除方法看完,然后再说这个方法

这里删除节点通过判断replacement != p,那么如果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);
}              

根据上面有种情况会相等,一种是sr为null的情况,另一种是被删除的节点没有左右节点的情况,这两种情况其实都是被删除的节点没有子节点。2-9行移除当前节点
11-12行,movable大部分情况都为true,moveRootToFront方法如上面所述

好了现在说一下balanceDeletion方法,如果被删除的是黑色,为了便于理解balanceDeletion方法,我们假设替换被删除的节点,会继承被删除节点的颜色,使被替换节点拥有2种颜色,如果被替换节点是红色现在就是红加黑,如果是黑色现在就是黑加黑,这里所说的继承只是一种假设,只是为了便于理解下面的代码。如果我们不做这个假设,而被删除的是黑节点,那么性质5就会被破坏。
分简单和复杂两种情况

简单:如果replacement现在是跟节点黑加黑,什么也不做。或者当前是红黑色,只需要把当前节点染成黑色,就恢复了红黑树的性质

复杂分四种情况:
1.replacement的兄弟节点是红色(注意,现在replacement以及替换到了被删除节点的位置,而且颜色肯定是黑加黑)
2.replacement的兄弟节点是黑色,而且两个孩子都是黑色节点
3.replacement的兄弟节点是黑色,但左节点为红色,右节点为黑色
4.replacement的兄弟节点是黑色,但右节点为红色,左节点为黑色
接下来根据代码来解决这些问题

 static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
                                           TreeNode<K,V> x) {
    for (TreeNode<K,V> xp, xpl, xpr;;)  {
        if (x == null || x == root)
            return root;
        else if ((xp = x.parent) == null) {
            x.red = false;
            return x;
        }
        else if (x.red) {
            x.red = false;
            return root;
        }
        else if ((xpl = xp.left) == x) {
            if ((xpr = xp.right) != null && xpr.red) {
                xpr.red = false;
                xp.red = true;
                root = rotateLeft(root, xp);
                xpr = (xp = x.parent) == null ? null : xp.right;
            }
            if (xpr == null)
                x = xp;
            else {
                TreeNode<K,V> sl = xpr.left, sr = xpr.right;
                if ((sr == null || !sr.red) &&
                    (sl == null || !sl.red)) {
                    xpr.red = true;
                    x = xp;
                }
                else {
                    if (sr == null || !sr.red) {
                        if (sl != null)
                            sl.red = false;
                        xpr.red = true;
                        root = rotateRight(root, xpr);
                        xpr = (xp = x.parent) == null ?
                            null : xp.right;
                    }
                    if (xpr != null) {
                        xpr.red = (xp == null) ? false : xp.red;
                        if ((sr = xpr.right) != null)
                            sr.red = false;
                    }
                    if (xp != null) {
                        xp.red = false;
                        root = rotateLeft(root, xp);
                    }
                    x = root;
                }
            }
        }
        else { // symmetric

参数root是上面传过来的根节点,参数x是上面传过来的替换被删除的节点
第3行,变量xp是x的父节点,xpl是xp的左节点,xpr是xp的右节点
4-13行,就是简单的情况
15-51行,就是复杂的4种情况。52行是对称的情况
15-20行,是上面所说的第一种情况,兄弟节点为红色,所以子节点必定是黑色,通过改变兄弟节点和x节点的颜色,然后通过左旋,就把情况转成了2、3或4了,因为x的新兄弟节点为黑色。19行对兄弟节点重新赋值
21-22行,如果兄弟节点为null的话,把父节点付给x,继续从下向上遍历,直到满足条件
24行,变量sl是兄弟节点的左节点,sr是兄弟节点的右节点
HashMap之TreeNode

25-29行,是上面所说的第二种情况,通过把兄弟节点设置为红色,并把x的黑色在遗传给父节点,通过把父节点赋值给x,继续向上遍历,直至满足上面所说简单的情况(也就是把上面所假设的双颜色变成了单颜色)
HashMap之TreeNode
蓝色只是我随便画的一个颜色,意义是这个颜色是什么颜色都行

31-38行,是上面所说的第三种情况,兄弟节点与兄弟节点的左节点颜色交换,然后右旋,现在就成了情况4
HashMap之TreeNode
39-48行,是上面所说的第四种情况,正常情况下这里xpr和xp都不会为null,改变兄弟节点和兄弟节点的又节点颜色,然后改变父节点颜色,左旋,去掉了x的双重黑色并满足红黑树的性质,48行,把root赋值给x,之后到简单情况的代码就退出循环了
HashMap之TreeNode

TreeNode与红黑树相关的操作就这些了,旋转、查找、出入、删除。为了满足HashMap重置内部数组大小,还有一个拆分方法。当HashMap需要扩容时候,就会调用拆分方法,把红黑树拆分成两个链表,如果两个链表大于一定的值,那么就还转成红黑树,否则就转换成链表

拆分

  final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
    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;
        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;
        }
    }

    if (loHead != null) {
        if (lc <= UNTREEIFY_THRESHOLD)
            tab[index] = loHead.untreeify(map);
        else {
            tab[index] = loHead;
            if (hiHead != null) // (else is already treeified)
                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);
        }
    }
}

如上所说这个方法在重置HashMap大小时候回被调用
参数map和tab没什么可说的和上面一样,index是拆分的树在tab数组中的位置,调用这个方法方法之前数组已经扩容了,参数bit是扩容之前的大小

10-25行,拆分一颗红黑树是通过用节点的hash值与bit进行并操作,然后把节点赋值给不同的链表,并通过lc和hc进行计数
28-45行,就是如果链表小于等于UNTREEIFY_THRESHOLD就把红黑树转换成Node链表,否则就转换成红黑树。转换成红黑树是通过treeify方法,转换成链表是通过untreeify这两个方法比较简单,条件判断什么的跟上面讲的都差不多

static final int UNTREEIFY_THRESHOLD = 6;
  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);
}    
inal 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;
}

到这里TreeNode就说完了,这样下一篇看HashMap分析的时候就会轻松很多了