HashMap的学习与总结

HashMap的 储存结构

hashmap底层的实现是数组加链表的存储结构,即数组里面的每一格都是一个链表。在java8中,链表的值超过阈值(8),存储结构就会变为红黑树。

为什么会用链表这种存储结构,链表的存储结构在存储数据时不会很慢吗。

这是因为,HashMap解决哈希冲突所用的办法,“拉链法”。Hash,一般翻译做“散列”,就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射。举个极端的例子,比如把100个数映射到10个数表示,那么必然会有不同的key指向同一个映射,为解决这个问题,有两种办法,一种为拉链法,一种,就为重hash。在hashmap里,当有不同的key映射到同一个值的时候,就把这个node放到链表里。每一个noed都会包含key value hash 和下一个节点(next)。

链表新的node节点在插入链表的时候,是怎么插入的?

java8之前是头插法,就是说新来的值会取代原有的值,原有的值就顺推到链表中去。

但是,在java8之后,都是所用尾部插入了。
为什么会有这种改变呢,这就要从hashmap的扩容说起。

HashMap是如何扩容的

HashMap的数组扩容,有两个因素 :

1.Capacity:HashMap当前长度。
2.LoadFactor:负载因子,默认值0.75f。

比如有100个值要储存,当储存到76个的时候经过判断需要扩容就进行扩容。
扩容具体分为两步:

1.扩容,创建一个空的Entry的空数组,长度是原来长度的两倍
2.ReHash,遍历原来的数组,并把原数组重新计算hash重新放入新的数组中。

为何需要重新hash呢,直接复制过去不可以吗

原因是hash的算法,经过了 index = HashCode(Key) & (Length - 1) length不一样结果当然会不一样

这里又涉及到了位与运算符简单补充一下:

运算规则:两个数都转为二进制,然后从高位开始比较,如果两个数都为1则为1,否则为0。
比如:129&128.
129转换成二进制就是10000001,128转换成二进制就是10000000。从高位开始比较得到,得到10000000,即128.

头插入的弊端就在于:链表头插法的会颠倒原来一个散列桶里面链表的顺序。在并发的时候原来的顺序被另外一个线程a颠倒了,而被挂起线程b恢复后拿扩容前的节点和顺序继续完成第一次循环后,又遵循a线程扩容后的链表顺序重新排列链表中的顺序,最终形成了环。

如果两个线程都发现HashMap需要重新调整大小,那么它们会同时试着去调整大小。在调整大小时,存储在链表中的元素的次序会反过来,因为在放入新的位置时,HashMap会将Entry对象不断的插入链表的头部。插入头部也主要是为了防止尾部遍历,否则这对key的HashCode相同的Entry每次添加还要定位到尾节点。如果条件竞争发送了,可能会出现环形链表,之后当我们get(key)操作时,就有可能发生死循环。

更加详细的解读过程请看:
链接: https://blog.csdn.net/HNUST_LIZEMING/article/details/89334204.

为何hashMap的默认值为16

hashmap里所有的key我们都会取他的hash,那怎么保证得到一个分布均匀的hash呢,分布均匀是为了*存储均匀
其实上边提到了求hash的时候进行了位与运算 : index = HashCode(Key) & (Length- 1)

因为在使用不是2的幂的数字的时候,Length-1的值是所有二进制位全为1,比如 : 15的二进制位: 1111 这种情况下,index的结果等同于HashCode后几位的值。
只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的。
这是为了实现均匀分布

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值