JDK1.8对HashMap进行了很多优化。
例如当一个槽位slot上的链表个数过多时,则会将链表转换为红黑树,以提高查询检索的效率。
访问节点方式:先找到节点所在的数组index索引位置,然后判断节点是什么结构进行遍历。
- 节点结构是非树型(链表)结构,通过节点的next遍历链表。
- 节点结构是树型(红黑树)结构,HashMap维护了2种节点之间的联系关系,分别是
- 链表方式:通过节点的next遍历链表。
- 红黑树方式:通过根节点root遍历红黑树。
一 链表->红黑树
树化阈值为8
static final int TREEIFY_THRESHOLD = 8;
最小树化容量值为64
static final int MIN_TREEIFY_CAPACITY = 64;
链表转化为红黑树需要满足2个条件
- 链表的节点数量(包括新增节点)大于等于树化阈值(查看源码可知,putVal方法是大于树化阈值,而其他方法是大于等于树化阈值)。
- HashMap的容量(Node数组的长度)大于等于最小树化容量值。
1.1 树化第一个条件
第一个条件:链表的节点数量(包括新增节点)大于等于树化阈值。
HashMap触发判断第一个条件的位置主要有4个方法,分别是putVal方法、computeIfAbsent方法、compute方法、merge方法。
1.1.1 putVal方法
查看putVal源码可知,根据key判断是否存在节点。若是不存在,则创建节点并加入到HashMap中,返回null。若是存在节点,则直接替换节点的旧值,并返回旧值。
根据key找到在Node数组的index位置,然后若该位置有节点,且节点连接方式是链表,则遍历链表寻找是否有对应key的节点。若是没有,则创建新节点加入到链表中,并判断当前槽位slot的链表的数量(数量包括新增的节点)是否大于树化阈值,链表节点数量大于树化阈值才会进入下一个树化判断,如下图。
1.1.2 computeIfAbsent方法
computeIfAbsent方法如果根据key找到对应节点,且节点的值不为null,则直接返回旧值,不更新节点的值。如果找不到对应节点或节点的值为null,则根据调用mappingFunction参数的apply方法得到新value,若得到的新value值不为null,则替换掉存在节点的值或者新增值为新value的节点,返回新value。
若是新增节点,且节点连接方式为链表,则判断链表的节点数量(包括新增节点)是否大于等于树化阈值。若是满足,则进入下一个树化条件判断。
1.1.3 compute方法
compute方法根据调用的remappingFunction参数的apply方法得到新value。若key的节点存在,且新value不为null,则更新节点的值,若新value为null,则删除该节点。若是节点不存在,且新value不为null,新增节点,返回新value。
若是新增节点,且节点连接方式为链表,则判断链表的节点数量(包括新增节点)是否大于等于树化阈值。若是满足,则进入下一个树化条件判断。
1.1.4 merge方法
merge方法若key的节点存在,且节点值不为null,调用的remappingFunction参数的apply方法得到新value,若节点值为null,则传入的valule为新value。若新value不为null,则更新节点的值,若新value为null,则删除该节点。若是节点不存在,且新value不为null,新增节点,返回新value。
若是新增节点,且节点连接方式为链表,则判断链表的节点数量(包括新增节点)是否大于等于树化阈值。若是满足,则进入下一个树化条件判断。
1.2 树化第二个条件
第二个条件:HashMap的容量(Node数组的长度)大于等于最小树化容量值。
满足树化第一个条件后,调用treeifyBin方法判断是否满足第二个树化条件。
若HashMap的node数组未初始化或者容量(node数组的长度)小于最小树化容量值,则不会将链表转换为红黑树,而是调用resize方法进行扩容操作。
若是node数组已初始化,且容量大于等于最小树化容量值,则将链表转换为红黑树。
二 红黑树->链表
非树化阈值为6
static final int UNTREEIFY_THRESHOLD = 6;
红黑树转换为链表只需满足以下2个条件之一便可
- 当删除红黑树的节点时,调用removeTreeNode方法。 在removeTreeNode方法中,判断如果根节点root、root.right、root.left、root.lelt.left其中一个为空,则认为该红黑树的节点个数太少了,不必采用红黑树结构,调用untreeify方法将红黑树转化为链表结构。
- 红黑树的节点数量小于等于非树化阈值。
当HashMap调用resize方法进行扩容时,如果slot槽位上的节点结构为红黑树,则调用节点的split方法重新分配节点在扩容后新数组的位置。
在split方法中, 通过(e.hash & bit) == 0(根据key的hash找到数组的位置的一种方式)来判断节点是否在同一个槽位slot。
- 若等于0,则表示该节点在扩容后的新数组的slot位置不变,也就是根据(n-1)&hash(n为扩容后的新数组的长度)找到新数组的index索引等于旧数组的index索引。
- 若是不等于0,则表示该节点在扩容后的新数组的slot位置变了,也就是根据(n-1)&hash(n为扩容后的新数组的长度)找到新数组的index索引改变了,新索引等于旧数组的index索引加上旧数组的长度。
通过(e.hash & bit) == 0公式拆分得到的loHead链表和hiHead链表。
- 若是其中链表一个为空,则说明红黑树的所有节点在扩容后的数组index位置相同,则可直接将红黑树移到对应索引位置,不用维护红黑树的结构,因为结构没变化,还是一样的平衡结构。
- 若是都不为空,则说明红黑树的节点分散了,平衡结构被破坏了,需要重新维护红黑树的平衡。
- 拆分后的链表个数若小于等于非树化阈值,说明红黑树的节点个数少了,无需维护红黑树结构,调用untreeify方法将红黑树转化为链表结构。
HashMap的源码解读可参考
深入理解HashMap(一)hashmap所用算法、构造函数_热爱健体的程序猿的博客-CSDN博客_hashmap的算法