HashMap-链表与红黑树转换触发条件

JDK1.8对HashMap进行了很多优化。

例如当一个槽位slot上的链表个数过多时,则会将链表转换为红黑树,以提高查询检索的效率。

访问节点方式:先找到节点所在的数组index索引位置,然后判断节点是什么结构进行遍历。

  • 节点结构是非树型(链表)结构,通过节点的next遍历链表。
  • 节点结构是树型(红黑树)结构,HashMap维护了2种节点之间的联系关系,分别是
  1. 链表方式:通过节点的next遍历链表。
  2. 红黑树方式:通过根节点root遍历红黑树。

一 链表->红黑树

树化阈值为8

static final int TREEIFY_THRESHOLD = 8;

最小树化容量值为64

static final int MIN_TREEIFY_CAPACITY = 64;

链表转化为红黑树需要满足2个条件

  1. 链表的节点数量(包括新增节点)大于等于树化阈值(查看源码可知,putVal方法是大于树化阈值,而其他方法是大于等于树化阈值)。
  2. HashMap的容量(Node数组的长度)大于等于最小树化容量值。

1.1 树化第一个条件

第一个条件:链表的节点数量(包括新增节点)大于等于树化阈值

HashMap触发判断第一个条件的位置主要有4个方法,分别是putVal方法、computeIfAbsent方法、compute方法、merge方法。

1.1.1 putVal方法

查看putVal源码可知,根据key判断是否存在节点。若是不存在,则创建节点并加入到HashMap中,返回null。若是存在节点,则直接替换节点的旧值,并返回旧值。

根据key找到在Node数组的index位置,然后若该位置有节点,且节点连接方式是链表,则遍历链表寻找是否有对应key的节点。若是没有,则创建新节点加入到链表中,并判断当前槽位slot的链表的数量(数量包括新增的节点)是否大于树化阈值,链表节点数量大于树化阈值才会进入下一个树化判断,如下图。

HashMap-链表与红黑树转换触发条件_第1张图片

1.1.2  computeIfAbsent方法

computeIfAbsent方法如果根据key找到对应节点,且节点的值不为null,则直接返回旧值,不更新节点的值。如果找不到对应节点或节点的值为null,则根据调用mappingFunction参数的apply方法得到新value,若得到的新value值不为null,则替换掉存在节点的值或者新增值为新value的节点,返回新value。

若是新增节点,且节点连接方式为链表,则判断链表的节点数量(包括新增节点)是否大于等于树化阈值。若是满足,则进入下一个树化条件判断。

HashMap-链表与红黑树转换触发条件_第2张图片

1.1.3 compute方法

compute方法根据调用的remappingFunction参数的apply方法得到新value。若key的节点存在,且新value不为null,则更新节点的值,若新value为null,则删除该节点。若是节点不存在,且新value不为null,新增节点,返回新value。

若是新增节点,且节点连接方式为链表,则判断链表的节点数量(包括新增节点)是否大于等于树化阈值。若是满足,则进入下一个树化条件判断。 

HashMap-链表与红黑树转换触发条件_第3张图片

1.1.4 merge方法

merge方法若key的节点存在,且节点值不为null,调用的remappingFunction参数的apply方法得到新value,若节点值为null,则传入的valule为新value。若新value不为null,则更新节点的值,若新value为null,则删除该节点。若是节点不存在,且新value不为null,新增节点,返回新value。

若是新增节点,且节点连接方式为链表,则判断链表的节点数量(包括新增节点)是否大于等于树化阈值。若是满足,则进入下一个树化条件判断。

HashMap-链表与红黑树转换触发条件_第4张图片

1.2 树化第二个条件

第二个条件:HashMap的容量(Node数组的长度)大于等于最小树化容量值

满足树化第一个条件后,调用treeifyBin方法判断是否满足第二个树化条件。 

若HashMap的node数组未初始化或者容量(node数组的长度)小于最小树化容量值,则不会将链表转换为红黑树,而是调用resize方法进行扩容操作。

若是node数组已初始化,且容量大于等于最小树化容量值,则将链表转换为红黑树。

HashMap-链表与红黑树转换触发条件_第5张图片

二 红黑树->链表

非树化阈值为6

    static final int UNTREEIFY_THRESHOLD = 6;

红黑树转换为链表只需满足以下2个条件之一便可

  • 当删除红黑树的节点时,调用removeTreeNode方法。 在removeTreeNode方法中,判断如果根节点rootroot.rightroot.leftroot.lelt.left其中一个为空,则认为该红黑树的节点个数太少了,不必采用红黑树结构,调用untreeify方法将红黑树转化为链表结构。

HashMap-链表与红黑树转换触发条件_第6张图片

  • 红黑树的节点数量小于等于非树化阈值。

当HashMap调用resize方法进行扩容时,如果slot槽位上的节点结构为红黑树,则调用节点的split方法重新分配节点在扩容后新数组的位置。

HashMap-链表与红黑树转换触发条件_第7张图片

split方法中, 通过(e.hash & bit) == 0(根据key的hash找到数组的位置的一种方式)来判断节点是否在同一个槽位slot

  • 等于0,则表示该节点在扩容后的新数组的slot位置不变,也就是根据(n-1)&hash(n为扩容后的新数组的长度)找到新数组的index索引等于旧数组的index索引。
  • 若是不等于0,则表示该节点在扩容后的新数组的slot位置变了,也就是根据(n-1)&hash(n为扩容后的新数组的长度)找到新数组的index索引改变了,新索引等于旧数组的index索引加上旧数组的长度

HashMap-链表与红黑树转换触发条件_第8张图片

通过(e.hash & bit) == 0公式拆分得到的loHead链表和hiHead链表。

  • 若是其中链表一个为空,则说明红黑树的所有节点在扩容后的数组index位置相同,则可直接将红黑树移到对应索引位置,不用维护红黑树的结构,因为结构没变化,还是一样的平衡结构。
  • 若是都不为空,则说明红黑树的节点分散了,平衡结构被破坏了,需要重新维护红黑树的平衡。
  • 拆分后的链表个数若小于等于非树化阈值,说明红黑树的节点个数少了,无需维护红黑树结构,调用untreeify方法将红黑树转化为链表结构。

HashMap-链表与红黑树转换触发条件_第9张图片

HashMap的源码解读可参考

深入理解HashMap(一)hashmap所用算法、构造函数_热爱健体的程序猿的博客-CSDN博客_hashmap的算法

tableSizeFor的理解_zwangsheng的博客-CSDN博客_tablesizefor

链表转红黑树的原因?为什么阈值为8?_菜鸟猫喵喵的博客-CSDN博客_阈值为什么是8

你可能感兴趣的:(java,链表,数据结构,java,红黑树,hashmap)