在jdk8中的hashmap,引入了红黑树的概念,那自然就会涉及到树化的过程,这篇笔记单独记录下树化的一些细节
结论
1、在达到树化的要求之后,会将当前单向链表转换为双向链表存储
2、然后从头到尾,依次拿着双向链表的节点,向红黑树中put
3、在put的过程中,可能会发生root节点的转换,也就是树的平衡,那如果root节点变化了之后,同时,会把新的root节点对应的元素,从双向链表中,移到头结点
4、所以,在hashmap中,一个红黑树,对应着一个双向链表,双向链表的头结点,永远是红黑树的root节点,在红黑树新增或者删除元素的时候,需要同步维护最新的root节点到双向链表的头结点
树化的条件
for (int binCount = 0; ; ++binCount) {
//这里是遍历到链表的最后一个
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
/**
* 这里这个树化阈值的判断需要仔细琢磨一下
* 如果当前链表有八个节点,刚刚将newNode插入到了尾结点后面,也就是说
* 此时的newNode是链表中的第九个节点,此时才会进行树化
* 因为bitCount是从0开始的,也就是说bitCount为7的时候,才会树化,此时已经有了八个节点
* newNode再插进来,当然是第九个了
*/
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
/**
* 在遍历的过程中,如果找到相同的key,就退出遍历,进行覆盖
* 但是不是在这里覆盖, 在下面判断中进行的覆盖
* 可以整体看下这里的逻辑,如果进入到下面的if判断中,那此时就会中断for循环,这时 e != null
* 如果这里的if判断一直没有进去,那就会进入到上面 if((e = p.next) == null)的判断,也就是说,e == null
*/
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
这是put方法中的部分代码,在当前插入的元素大于8个时,会进行树化
树化的过程
/**
* 链表转换红黑树,进行树化的目的是将链表中节点的查询效率提高
* 1.需要满足两个条件:数组不为空且hashmap数组的长度超过64,否则,只会进行扩容,不会进行树化
* 如果数组长度不超过64,就可以进行扩容,因为扩容之后,链表的长度就会变为一半
* 2.e表示的是数组中某个链表对应的头结点
* 2.1 do while循环,会把链表中每个node节点进行转换,转换成treeNode,然后将链表变成了双向链表,有prev和next,hd表示的是原链表的第一个节点,
* 2.2 然后根据hd进行真正的红黑树转换,hd就是红黑树初始化的根节点
*
* 所以:链表转红黑树,首先会把当前的单向链表,转换成双向链表,然后再拿着双向链表的头结点hd去进行红黑树的转换
*
* @param tab
* @param hash:待插入元素key值对应的hash
*/
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) <