jdk1.8的hashmap真的是大于8就转换成红黑树,小于6就变成链表吗
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jdk1.8的hashmap真的是大于8就转换成红黑树,小于6就变成链表吗相关的知识,希望对你有一定的参考价值。
参考技术A 本文夹杂部分笔者个人观点,如描述有误,欢迎指正写这篇文章,是因为最近研究hashmap源码的时候,会结合网上的一些博客来促进理解。而关于红黑树和链表相互转换这一块,大部分的文章都会这样描述:hashmap中定义了两个常量:
当链表元素个数大于8的时候,就会转换为红黑树;当红黑树元素个数小于6的时候,就会转换回链表。
笔者通过仔细观察,发现这种说法并不严谨。hashMap中确实定义了这两个常量,但并非简单通过元素个数的判断来进行转换。
链表转换为红黑树的最终目的,是为了解决在map中元素过多,hash冲突较大,而导致的读写效率降低的问题。在源码的putVal方法中,有关红黑树结构化的分支为:
即网上所说的,链表的长度大于8的时候,就转换为红黑树,我们来看看treeifyBin方法:
可以看到在treeifyBin中并不是简单地将链表转换为红黑树,而是先判断table的长度是否大于64,如果小于64,就通过扩容的方式来解决,避免红黑树结构化。原因呢?笔者个人觉得链表长度大于8有两种情况:
第二种情况是可以用扩容的方式来避免的,扩容后链表长度变短,读写效率自然提高。另外,扩容相对于转换为红黑树的好处在于可以保证数据结构更简单。
由此可见并不是链表长度超过8就一定会转换成红黑树,而是先尝试扩容
基本思想是当红黑树中的元素减少并小于一定数量时,会切换回链表。而元素减少有两种情况:
hashMap的remove方法,会进入到removeNode方法,找到要删除的节点,并判断node类型是否为treeNode,然后进入删除红黑树节点逻辑的removeTreeNode方法中,该方法有关解除红黑树结构的分支如下:
可以看到,此处并没有利用到网上所说的,当节点数小于UNTREEIFY_THRESHOLD时才转换,而是通过红黑树根节点及其子节点是否为空来判断。而满足该条件的最大红黑树结构如下:
节点数为10,大于 UNTREEIFY_THRESHOLD(6),但是根据该方法的逻辑判断,是需要转换为链表的
resize的时候,判断节点类型,如果是链表,则将链表拆分,如果是TreeNode,则执行TreeNode的split方法分割红黑树,而split方法中将红黑树转换为链表的分支如下:
这里才用到了 UNTREEIFY_THRESHOLD 的判断,当红黑树节点元素小于等于6时,才调用untreeify方法转换回链表
关于HashMap的加载因子相关理解
HashMap在JDK1.7是以数组加链表的形式组成,JDK1.8后新增了红黑树结构,当链表大于8并且容量大于64时,链表结构会转成红黑树结构。
JDK1.8 之所以会加入红黑树是因为当链表过长是会严重影响HashMap的性能,而红黑树具有快速增删改查的特点。
HashMap源码中包含了一下几个常量的定义:
/** * The default initial capacity - MUST be a power of two. * 初始化长度 */ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 /** * The maximum capacity, used if a higher value is implicitly specified * by either of the constructors with arguments. * MUST be a power of two <= 1<<30. * 最大长度 */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * The load factor used when none specified in constructor. * 默认扩容加载因子 */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * The bin count threshold for using a tree rather than list for a * bin. Bins are converted to trees when adding an element to a * bin with at least this many nodes. The value must be greater * than 2 and should be at least 8 to mesh with assumptions in * tree removal about conversion back to plain bins upon * shrinkage. * 当链表长度大于此值且容量大于64时 */ static final int TREEIFY_THRESHOLD = 8; /** * The bin count threshold for untreeifying a (split) bin during a * resize operation. Should be less than TREEIFY_THRESHOLD, and at * most 6 to mesh with shrinkage detection under removal. * 转换链表的临界值,当元素小于此值时,会将红黑树结构转换为链表结构 */ static final int UNTREEIFY_THRESHOLD = 6; /** * The smallest table capacity for which bins may be treeified. * (Otherwise the table is resized if too many nodes in a bin.) * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts * between resizing and treeification thresholds. * 最小树容量 */ static final int MIN_TREEIFY_CAPACITY = 64;
关于加载因子
加载因子也叫作扩容因子,用来判断什么时候进行扩容,假设加载因子为0.75,HashMap的初始容量为16,当HashMap中有16 * 0.75 = 12个容量时,HashMap就会进行扩容。
如果加载因子越大,扩容发生的频率就会比较低,占用空间比较小,但是发生hash冲突的几率会提升,对元素操作时间会增加,运行效率降低;
如果加载因子太小,那么表中的数据将过于稀疏(很多空间还没用,就开始扩容了),对空间造成严重浪费;
而且因为容量默认为2的次方,当加载因子为0.75时,容量和加载因子的乘积为整数。
所以系统默认加载因子取了0.5 -1 之间的0.75.
以上是关于jdk1.8的hashmap真的是大于8就转换成红黑树,小于6就变成链表吗的主要内容,如果未能解决你的问题,请参考以下文章