HashMap之链表转红黑树(树化 )-treefyBin方法源码解读(所有涉及到的方法均有详细解读,欢迎指正)

PS:由于文档是我在本地编写好之后再复制过来的,有些文本格式没能完整的体现,故提供下述图片,供大家阅览,以便有更好的阅读体验:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

HashMap之链表转红黑树(树化 )-treefyBin方法
方法概述:先将链表节点转为树节点,再将都是红黑树节点的链表转为红黑树
分析HashMap的put方法的源码时发现,当HashMap中某个链表上存储的元素个数达到TREEIFY_THRESHOLD(树化阈值)=8个时,会调用treeifyBin方法尝试将该链表转换成红黑树。
(PS:为什么说是尝试,而不是直接转呢?因为,在做转换之前,还会对HashMap的链表数组存储的元素个数进行判断,当HashMap存储的总元素个数达到MIN_TREEIFY_CAPACITY(树化最小容量,HashMap存储的元素总个数)=64时,才会进行链表到红黑树的转换;否则,只进行扩容。)
概括起来,将链表转为红黑树主要有以下处理:
 判断链表中存储的元素个数是否到达树化阈值8,没达到不往下处理
 判断HashMap中链表数组的长度是否达到树化最小容量64,没达到则扩容,而非转红黑树
 将需要树化的链表中的所有节点替换成树节点。
 将所有节点已经是红黑树节点的链表转为红黑树
具体,详见下述的源码解析:

1. HashMap的put(K,V)方法中调用treeifyBin()的源码

方法概述:链表长度达到树化阈值时,开始调用treeifyBin(tab,hash)尝试转红黑树
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st//当链表中存储的元素个数达到(等于)树化阈值=8时,开始链表转红黑树的处理
treeifyBin(tab, hash); //开始尝试将链表结构转成红黑树结构,但还保留有链表的顺序结构,具体方法源码详见第2点
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}

2. HashMap的treeifyBin(tab,hash)方法的源码

方法概述:HashMap数组长度达到树化最小容量,开始树化操作:先将链表中的节点都替换成树节点,再将树节点组成的链表转为红黑树
Replaces all linked nodes in bin at index for given hash unless table is too small, in which case resizes instead.将给定hash索引的桶中的所有链表节点替换掉,除非数组太小的话,就用扩容方法替
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)//当HashMap存储链表头节点的数组为null或其长度达不到(小于等于)树化最小容量=64时,则进行扩容而非树化操作。
resize();//调用扩容方法
else if ((e = tab[index = (n - 1) & hash]) != null) {//hash指定的数组索引位置的头节点不为空才继续
TreeNode<K,V> hd = null, tl = null; //hd代表处理过程中需要被转成红黑树的链表的头节head,tl代表处理过程中需要被转为红黑树节点的链表的实时最新当前尾节点tail
do {
TreeNode<K,V> p = replacementTreeNode(e, null); //将链表类型Node<K,V>的节点转成树类型TreeNode<K,V>的节点,具体方法源码详见第3点。
if (tl == null) //tl尾节点为null,说明还没开始第一次处理(注:每次有一个链表头节点走进do{}while{}循环体,tl只会在第一次的时候为null,因为后续会将非null的p=replacementTreeNode(e(非null),null)赋给了tl,这样可以保证只有链表头节点才会赋给hd)
hd = p; //把头节点赋给hd
else {
p.prev = tl; //将当前要处理的非头节点的上一节点指向实时最新当前尾节点tl
tl.next = p; //将实时最新当前尾节点tl的下一节点指向当前要处理的非头节点
}
tl = p;//将当前已经处理完的非头节点置为最新的尾节点tl
} while ((e = e.next) != null); //有后续节点时继续循环,否则退出循环
if ((tab[index] = hd) != null) //处理后的hd头节点不为空,才有料,才需转红黑树
hd.treeify(tab); //将已经全部被替换为树类型节点的链表(头节点hd可以代表链表,因为通过链表头节点的next指向可以遍历整个链表)转为红黑树,具体方法源码详见第4点
}
}

3. HashMap的treeifyBin()方法中调用replacementTreeNode(e,null)的源码

方法概述:将链表节点转为树节点
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
return new TreeNode<>(p.hash, p.key, p.value, next); //为树化操作做前期准备:将链表类型Node<K,V>每个节点都转成树类型TreeNode<K,V>节点(注:如果再深入一些,会发现一个很有意思的事情,原来HashMap中的TreeNode<K,V> extends LinkedHashMap.Entry<K,V> extends HashMap.Node<K,V>,构造方法层层往上调用又回到了HashMap的Node<K,V>,说明TreeNode既是树类型结构,同时也是链表类型结构)
}
/**HashMapKaTeX parse error: Expected '}', got 'EOF' at end of input: …点和下一节点(从HashMapNode继承而来),所以是双向链表
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
/…红黑树的其他相关代码省略…/
}
/*LinkedHashMapEntry<K,V>//HashMap.NodesubclassfornormalLinkedHashMapentries./staticclassEntry<K,V>extendsHashMap.Node<K,V>Entry<K,V>before,after;Entry(inthash,Kkey,Vvalue,Node<K,V>next)super(hash,key,value,next);/HashMapEntry<K,V>的构造方法源码*/ /*** HashMap.Node subclass for normal LinkedHashMap entries. */ static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after; Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } } /**HashMapNode<K,V>的构造方法源码/
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next; //由于HashMap的Node只存了下一节点,所以是单向链表
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
/…链表的其他相关代码省略…/
}

4. HashMap%TreeNode的treeify(tab)方法源码

方法概述:将都是树节点的链表转为红黑树,这里才是真正的树化操作
Forms tree of the nodes linked from this node
final void treeify(Node<K,V>[] tab) {
TreeNode<K,V> root = null;//定义树根节点
for (TreeNode<K,V> x = this, next; x != null; x = next) {//x=hd手树链表头节点
next = (TreeNode<K,V>)x.next; //下一节点,保证在链表中循环递进
x.left = x.right = null; //红黑树左右分支节点先初始化为null
if (root == null) {//root只会等于一次null,后续不会再进到这里
x.parent = null; //根节点无父节点
x.red = false; //根节点非红,即黑
root = x; //将头节点赋给根节点
}
else {//处理完头节点/根节点后,处理其他后续节点
K k = x.key; //k代表键key
int h = x.hash; //h代表hash值
Class<?> kc = null; //kc代表键对应的类型
for (TreeNode<K,V> p = root; ; ) {//目前循环只定义了从root根节点出发
int dir, ph; //dir决定节点属于左子树节点还是右子树节点;ph代表p节点的hash码
K pk = p.key; //pk代表p节点的key
if ((ph = p.hash) > h) //根据hash码大小比较,决定向左还是向右
dir = -1; //更大的向右走
else if (ph < h)
dir = 1; //更小的向左走
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||//该方法用于判断键k所属类型是否属于Comparable接口的实现类型,是则返回具体类型,不是则返回null;具体方法源码详见第5点
(dir = compareComparables(kc, k, pk)) == 0) //该方法用于返回k和pk的比较结果,如果x和k的类型kc都是可比较类型,则直接返回k和pk的比较结果,否则返回0,具体方法源码详见第6点
dir = tieBreakOrder(k, pk); //该方法用于返回k和pk的决胜局最终PK结果,具体方法源码详见第7点
TreeNode<K,V> xp = p; //存留p的值,用于后续处理
if ((p = (dir <= 0) ? p.left : p.right) == null) {//根据dir判断元素应该放入左分支还是右分支,左右分支若已存在元素,则继续循环递进前进进行k比较 (从p=root根节点到p.left左分支或p.right右分支再到下一级的左分支或右分支)
x.parent = xp; //x的父节点是xp即p(x肯定是root后面的节点,因为if(root=null)里root=x处理完后,x会走到next=x.next,往下走)(x.next和p.left/p.right将链表和红黑树串起来了)
if (dir <= 0) //小于等于0属于左节点
xp.left = x; //将链表节点转为红黑树左节点
else
xp.right = x; //大于0属于右节点, 将链表节点转为红黑树右节点
root = balanceInsertion(root, x); //该方法用于确保插入元素后的红黑树实现自平衡(因为新插入元素后可能会打破红黑树原有的平衡(五大原则)),具体方法源码详见第8点
break;
}
}
}
}
moveRootToFront(tab, root); //该方法用于确保根节点始终是头节点,具体方法源码详见第9点
}

5. HashMap%TreeNode的comparableClassFor(k)方法源码

方法概述:该方法用于判断键k所属类型是否属于Comparable(可比较)接口的实现类型,是则返回具体类型,不是则返回null
Returns x’s Class if it is of the form “class C implements Comparable”, else null.
//判断x的类型是否属于Comparable接口的实现类,不是则返回null
static Class<?> comparableClassFor(Object x) {
if (x instanceof Comparable) {
Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
//c用于存x的类型;ts用于存c实现的所有父接口;p用于存储ts的参数化接口类型
if ((c = x.getClass()) == String.class) // bypass checks //判断x的类型是否属于String类型
return c;//由于String类型是Comparable接口的实现类,所以直接返回
if ((ts = c.getGenericInterfaces()) != null) {//取出c的所有父接口信息并存到ts中
for (int i = 0; i < ts.length; ++i) {
if (((t = ts[i]) instanceof ParameterizedType) &&//某一父接口属于参数化接口
((p = (ParameterizedType)t).getRawType() ==Comparable.class) &&//并且该参数化类型接口就是Comparable接口
(as = p.getActualTypeArguments()) != null && as.length == 1 && as[0] == c) // type arg is c //并且该参数化接口的参数类型数组不为空,且其长度为1,说明数组中只有唯一一个类型,就是x的类型c
return c; //返回x的类型c
}
}
}
return null; //否则返回null,说明x的类型不属于Comparable接口的实现类
}

6. HashMap%TreeNode的compareComparables(kc, k, pk)方法源码

方法概述:该方法用于返回k和pk的比较结果,如果x和k的类型kc都是可比较类型,则直接返回k和pk的比较结果,否则返回0
Returns k.compareTo(x) if x matches kc (k’s screened comparable class), else 0.
//如果x和k都是可比较类型,则返回k和x的比较结果,否则返回0

@SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable
static int compareComparables(Class<?> kc, Object k, Object x) {
    return (x == null || x.getClass() != kc ? 0 : //(结合第5点看,走到这里说明k的类型kc是可比较类型)若x等于null或者x的类型不等于kc,说明x和k不具有可比性,此时会返回0
            ((Comparable)k).compareTo(x)); //走到这里说明x不等于null并且x的l和k是同一类型,即均为可比较的类型,开始用compareTo方法进行比较,返回-1/0/1.

}

7. HashMap%TreeNode的tieBreakOrder(k, pk)方法源码

方法概述:该方法用于返回k和pk的决胜局最终PK结果
Tie-breaking utility for ordering insertions when equal hashCodes and non-comparable.
//决胜局功能是为了在hashCode相等且没法比较时,用于决定如何顺序插入
We don’t require a total order, just a consistent insertion rule to maintain equivalence across
//我们不需要全序,只需通过再平衡,用一个始终如一保持一致的插入规则来维持平衡
rebalancings. Tie-breaking further than necessary simplifies testing a bit.
/决胜局进一步简化了测试。/
static int tieBreakOrder(Object a, Object b) {
int d; //初始值为0
if ( a == null || b == null ||//当a或b两者有一个为null(即没法直接相互比较)
(d = a.getClass().getName().compareTo(b.getClass().getName())) == 0) //或者两者类型相同
d = (System.identityHashCode(a) <= System.identityHashCode(b) ? -1 : 1//开始调用System.identityHashCode对a和b两者间进行终极PK,小于等于返回-1,大于返回1,并将结果赋给d
);
return d; //返回最终PK结果(可能是-1/0/1)
}

8. HashMap%TreeNode的balanceInsertion(root, x)方法源码

方法概述:保证在插入元素后红黑树仍能保持其自平衡,发现冲突则开始再平衡调节
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) {//父节点为null,说明x为根节点
x.red = false; //根节点是黑色
return x; //返回是根节点的x
}
else if (!xp.red || (xpp = xp.parent) == null) //x的父节点xp为黑色(与x是红黑不冲突)或x的祖父节点xpp为null(xpp是根节点,即root),这两种情况不会引发红黑树不平衡,故直接返回root根节点
return root;
//后续xp是红色(冲突来啦!x红且xp红,不满足红黑树自平衡规则3),因为xp是黑色的话,在上面的else if里就返回了,程序不会再继续往下进行了
//xpp非null,原因同上
//下面开始分情况处理:
if (xp == (xppl = xpp.left)) {//x的父节点xp是x的祖父节点xpp的左子节点xppl
if ((xppr = xpp.right) != null && xppr.red) {//x的伯父节点不为空且为红色:开始变色,将父节点和伯父节点变为黑色,将祖父节点变为红色即可
xppr.red = false; //伯父节点变为黑色
xp.red = false; //父节点变为黑色
xpp.red = true; //祖父节点变为红色
x = xpp; //继续下一轮循环
}
else {//走到这里说明x的伯父节点为空或是黑色
if (x == xp.right) {//x是其父节点xp的右孩子节点(x和xp均红,开始左旋调整)
root = rotateLeft(root, x = xp); //左旋xp节点(x和xp是两个连续的红色节点,x为xp的右子节点,则开始左旋).注意:左旋后x和xp的父子关系互换,具体方法源码详见第10点
xpp = (xp = x.parent) == null ? null : xp.parent; //左旋后xp和xpp需要根据跟原xp互换父子关系后的x重新定义赋值,左旋后x变成了xp的父节点,xp为x的子节点,所以这里的x.parent实际其实指的是原xp现在的父节点x,但是在业务逻辑上,现在的x应该被定义为xp.故xp=x.parent,则xpp=新xp.parent.
}
if (xp != null) { //左旋后的新的xp若不为空
xp.red = false; //将xp置为黑色,这样x和xp就不再是连续的两个红色节点了
if (xpp != null) { //若xpp不为空
xpp.red = true; //统一将xpp设置为红色,配合下面的右旋保持一致操作
root = rotateRight(root, xpp); //右旋xpp节点(可能会导致xpp和其父节点变成两个连续的红色节点,由于xp为xpp的左子节点,所以尝试开始右旋),具体方法源码详见第11点
}
}
}
}
else {//x的父节点xp是x的祖父节点xpp的右子节点xppr,处理逻辑同上,不再赘述…
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);
}
}
}
}
}
}

9. HashMap%TreeNode的moveRootToFront (tab, root)方法源码

方法概述:该方法用于确保根节点始终是头节点
/*** Ensures that the given root is the first node of its bin.*/
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; //将根节点的上一节点设为null
}
assert checkInvariants(root); //检查根节点是否满足红黑树节点规则, 具体方法源码详见第12点
}
}

10. HashMap%TreeNode的rotateLeft(root, x = xp)方法源码

方法概述:该方法用于左旋调整红黑树(以p节点为中心左旋) ,会发生父子关系互换
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root, TreeNode<K,V> p) {
TreeNode<K,V> r, pp, rl;
if (p != null && (r = p.right) != null) { //p不为空,且p的右孩子节点不为空
if ((rl = p.right = r.left) != null) //将p的右孩子节点r的左孩子节点rl设为p的右孩子节点
rl.parent = p; //将p的右孩子节点r的左孩子节点rl的父节点设为p
if ((pp = r.parent = p.parent) == null)//将p的右孩子的父节点设为p的父节点
(root = r).red = false; //若p为根节点,左旋用p的右孩子节点替换p后,则将p的右孩子节点设为黑色
else if (pp.left == p) //若p的父节点不为空,且p为其父节点pp的左孩子节点
pp.left = r; //则将p父节点的左孩子节点替换成p节点原来的右孩子节点r
else//若p的父节点不为空,且p为其父节点pp的右孩子节点
pp.right = r; //则将p父节点的右孩子节点替换成p节点原来的右孩子节点r
r.left = p; //p成为其原右孩子节点的左孩子节点
p.parent = r; //p的父节点为其原右孩子节点
}
return root;
}
示意图:
在这里插入图片描述

总结:左旋后,p节点成为了其右子节点的左子节点;其右子节点的左节点成了p的右子节点。
相当于r用rl跟p换了个位置

11. HashMap%TreeNode的rotateRight(root, p)方法源码

方法概述:该方法用于右旋调整红黑树(以p节点为中心右旋) ,会发生父子关系互换
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) { //p不为空,且p的左孩子节点不为空
if ((lr = p.left = l.right) != null) //将p的左孩子节点l的右孩子节点lr设为p的左孩子节点
lr.parent = p; //将p的左孩子节点l的右孩子节点lr的父节点设为p
if ((pp = l.parent = p.parent) == null) //将p的左孩子的父节点设为p的父节点
(root = l).red = false; //若p为根节点,右旋用p的左孩子节点替换p后,则将p的左孩子节点设为黑色
else if (pp.right == p) //若p的父节点不为空,且p为其父节点pp的右孩子节点
pp.right = l; //则将p父节点的右孩子节点替换成p节点原来的左孩子节点l
else //若p的父节点不为空,且p为其父节点pp的左孩子节点
pp.left = l; //则将p父节点的左孩子节点替换成p节点原来的左孩子节点l
l.right = p; //p成为其原左孩子节点的右孩子节点
p.parent = l; //p的父节点为其原左孩子节点
}
return root;
}
示意图:
在这里插入图片描述
总结:右旋后,p节点成为了其左子节点的右子节点;其左子节点的右节点成了p的左子节点。相当于l用lr跟p换了个位置。

12. HashMap%TreeNode的checkInvariants (root)方法源码

方法概述:该方法用于校验插入或删除后的链表/红黑树是否正常
/*** Recursive invariant check*/
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) //t.prev的下一个节点不是t
return false;
if (tn != null && tn.prev != t) //t.next的上一个节点不是t
return false;
if (tp != null && t != tp.left && t != tp.right) //t既不是t.parent的左子节点也不是右子节点
return false;
if (tl != null && (tl.parent != t || tl.hash > t.hash)) //t的左子节点的父节点不是t,或t的左子节点的hash值大于t的hash值
return false;
if (tr != null && (tr.parent != t || tr.hash < t.hash)) //t的右子节点的父节点不是t,或t的右子节点的hash值小于t的hash值
return false;
if (t.red && tl != null && tl.red && tr != null && tr.red) //t为红色节点且t的左右子节点均为红(父节点和其中的一个子节点为红,还可以通过左旋右旋调整,两个都为红色说明肯定有问题)
return false;
if (tl != null && !checkInvariants(tl)) //对t的左子节点tl继续上述检查
return false;
if (tr != null && !checkInvariants(tr)) //对t的右子节点tr继续上述检查
return false;
return true; //上述检查都没问题,才返回true
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章