分析&回答
一张图看懂
- JDK1.7:数组+链表
- JDK1.8:数组+链表+红黑树
看下图:
什么时候链表会变成红黑树
链表长度大于等于8时转成了红黑树,转成红黑树是为了遵循泊松分布。
HashMap源码中注释如下,可以看出按照泊松分布的计算公式计算出了链表中元素个数和概率的对照表
* 0: 0.60653066
* 1: 0.30326533
* 2: 0.07581633
* 3: 0.01263606
* 4: 0.00157952
* 5: 0.00015795
* 6: 0.00001316
* 7: 0.00000094
* 8: 0.00000006
可以看到链表中元素个数为8时的概率已经非常小。
另一方面红黑树平均查找长度是log(n),长度为8的时候,平均查找长度为3,
如果继续使用链表,平均查找长度为8/2=4,这才有转换为树的必要。
链表长度如果是小于等于6,6/2=3,虽然速度也很快的,但是链表和红黑树之间的转换也很耗时。
虽然在hashmap底层有这种红黑树的结构,但是我们要知道能产生这种结构的概率也不大,所以我们知道在 JDK1.7 到 JDK1.8 这其中HashMap的性能也只提高了7%~8% 左右。
反思&扩展
HashMap 的默认初始容量是多少?HashMap 的容量有什么限制吗?
默认初始容量是16。HashMap 的容量必须是2的N次方,HashMap 会根据我们传入的容量计算一个大于等于该容量的最小的2的N次方,例如传new HashMap<>(9); 容量大小为16。
为什么加载因子是0.75?
如果设置的太大,比如 1,这就意味着数组的每个空位都需要填满,即达到理想状态,不产生链表,但实际是不可能达到这种理想状态,如果一直等数组填满才扩容,虽然达到了最大的数组空间利用率,但会产生大量的哈希碰撞,同时产生更多的链表,显然不符合我们的需求。
如果设置的过小,比如 0.5,这样一来保证了数组空间很充足,减少了哈希碰撞,这种情况下查询效率很高,但消耗了大量空间。
因此,我们就需要在时间和空间上做一个折中,选择最合适的负载因子以保证最优化,取到了0.75
HashMap 1.7和1.8除了上面变化还有啥变化?
- 1.7扩容时需要重新计算哈希值和索引位置,1.8并不重新计算哈希值,巧妙地采用和扩容后容量进行&操作来计算新的索引位置。
- 1.7是采用表头插入法插入链表,1.8采用的是尾部插入法。
- 在1.7中采用表头插入法,在扩容时会改变链表中元素原本的顺序,以至于在并发场景下导致链表成环的问题;在1.8中采用尾部插入法,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了。
喵呜面试助手: 一站式解决面试问题,你可以搜索微信小程序 [喵呜面试助手] 或关注 [喵呜刷题] -> 面试助手 免费刷题。如有好的面试知识或技巧期待您的共享!