学一学HashMap 1.7

一、底层结构

1.7 数组加链表
1.8 数组加链表加红黑树(单向链表)

二、源码分析的一些疑问点(1.7)

发生冲突时,链表采用头插法。

头插法一定比尾插好吗?

不见得,因为你总会去遍历一次链表,看是否有这个元素,没有才需要插入。
key是允许为null的。

补充知识:二的幂次方的数,用二进制表达,所有位上,只有1个位是1。如8,就是1000.

为什么容量是2的幂次方?

n是容量,(n - 1) & hash实际上是计算出 key 在数组中索引位置。用这个与运算代替了取模运算。
(n - 1) & hash,当n为2次幂时,会满足一个公式:(n - 1) & hash = hash % n

由于取模的预算没有位运算快,因此为了性能这么设计也合理。

因此我们即使设定容量不为幂,底层也会帮助我们改成2的幂次。如我们设定为10,实际容量会是16.

key能为null吗?

key为null,会存在数组中的索引为0的位置。
HashMap对象的key、value值均可为null。但显然只能有一个Key为null。
HahTable对象的key、value值均不可为null。

且两者的的key值均不能重复,若添加key相同的键值对,后面的value会自动覆盖前面的value,但不会报错。

扩容为什么是2倍

可以和容量是2的幂次方的问题相同

1.7扩容线程不安全时会出现循环链表

如果链表中为1->2->3,那么扩容可能形成3->2->1,因为是头插法,所有顺序都颠倒了。中间某个线程不安全就会导致头尾相连。

在多线程环境下,1.7 会产生死循环、数据丢失、数据覆盖的问题,1.8 中会有数据覆盖的问题。(改成尾插也是有问题的)

负载因子0.75

当map中的所有元素个数超过数组容量的0.75倍,就会扩容。
那为什么是0.75呢,反正大了为1容易冲突,小了0.5容易浪费空间,0.75是一个折中的考虑。为什么不是0.6或者0.8,这个可以根据大量的数据插入计算出效率,0.75可能折中是最佳。

fast-fail机制

当多个线程对同一个集合进行操作的时候,某线程访问集合的过程中,该集合的内容被其他线程所改变(即其它线程通过add、remove、clear等方法,改变了modCount的值);这时,就会抛ConcurrentModificationException异常,产生fail-fast事件。

1.7 扩容

1.7扩容时需要重新计算哈希值和索引位置,1.8并不重新计算哈希值,巧妙地采用和扩容后容量进行&操作来计算新的索引位置。

三、源码分析(1.8)

2.1 HashMap的几个重要默认参数

初始数组容量默认为16,负载因子为0.75。也就是说大小为16的HashMap的数组中,有数据量超过13时,就会扩容成32。

超过8个转化成红黑树?

在理想情况下,使用随机哈希码,节点出现的频率在hash桶(形成的链表)中遵循泊松分布。也就是说链表长度超过8个,概率是百万分之6,但是超过8个影响了性能,因此能不转换成红黑树就不要转化。

变树变链表

链表长度超过8个就会将链表转换为红黑树,长度低于6个就会将红黑树转化成链表。这里可以理解成避免扩容缩容会产生震荡。扩一次缩一次。为什么是8呢,因为太长会降低性能。之前也分析过,超过8的情况很少,但是还是有,如果有那就转用更高效的红黑树。

图片说明

1.8扩容机制

1.7扩容是重新计算,但是1.8不同,利用新增的一位要么是1要么是0,决定元素要么在原来的位置,要么在原位置在移动2次幂的位置。这样也保证了均匀性。

全部评论

相关推荐

03-10 14:19
已编辑
重庆邮电大学 前端工程师
球Offer上岸👑:测试也难求一面 逆天
点赞 评论 收藏
分享
AI牛可乐:哇塞,恭喜恭喜!48万的年薪,真是让人羡慕呀!看来你找到了一个超棒的工作,可以享受不卷的生活啦!🎉有没有什么求职秘诀想要分享给小牛牛呢?或者,想不想知道我是谁呢?😉(点击我的头像,我们可以私信聊聊哦~)
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务