HashMap底层源码实现

首先需要明确的是

HashMap 内部结构:可以看作是数组和链表结合组成的复合结构,数组被分为一个个桶(bucket),每个桶存储有一个或多个Entry对象,每个Entry对象包含三部分:key(键)、value(值),next(指向下一个Entry),通过哈希值决定了Entry对象在这个数组的寻址;哈希值相同的Entry对象(键值对),则以链表形式存储。如果链表大小超过树形转换的阈值(TREEIFY_THRESHOLD= 8),链表就会被改造为树形(红黑树)结构。

问:那HashMap在存储键值对时,是如何确定存储的位置的?

答:通过hashcode()方法计算键的哈希值==>得到的是一个整数。

int hash = key.hashcode()

       JDK1.8中采用的是位运算。在特定情况下,位运算可以转换成取模运算(当 b = 2^n 时,a % b = a & (b - 1) )。也是因此,HashMap 才将初始长度设置为 16,且扩容只能是以 2 的倍数(2^n)扩容。源码中使用位运算hash & (length - 1) 代替取模运算hash%length,是利用其可以直接对内存数据进行操作,不需要转换成十进制,来提高运算效率。

static int indexFor(int h, int length) {
 
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
 
return h & (length-1);
 
}

问:若得到的数组下标相同怎么办?会如何插入?

答:如果该位置已有元素存储,会插入在链表的头部。

// 将新进元素指向链表的头部,再将头节点指向新元素
table[i] = new Entry(key,value,table[i]);

 这里写图片描述

问:什么时候进行扩容?

答:当hashmap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,也就是说,默认情况下,数组大小为16,那么当hashmap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知hashmap中元素的个数,那么预设元素的个数能够有效的提高hashmap的性能。比如说,我们有1000个元素new HashMap(1000), 但是理论上来讲new HashMap(1024)更合适,不过上面annegu已经说过,即使是1000,hashmap也自动会将其设置为1024。 但是new HashMap(1024)还不是更合适的,因为0.75*1000 < 1000, 也就是说为了让0.75 * size > 1000, 我们必须这样new HashMap(2048)才最合适,既考虑了&的问题,也避免了resize的问题。

问:怎么进行扩容?(JDK1.8)

答: 图(a)表示扩容前的key1和key2两种key确定索引位置的示例,图(b)表示扩容后key1和key2两种key确定索引位置的示例,其中hash1是key1对应的哈希与高位运算结果。

我们在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”,可以看看下图为16扩充为32的resize示意图:

 

问:为什么hashMap的容量扩容时一定是2的幂次方?

答:首先需要知道什么是与运算,都为1,才能得到1.我们在计算得出hash值后,是如何计算存储的位置的。利用的与运算,参考上面。

若HashMap容量为15,length-1的二进制位1110

那么两个索引的位置都是14,就会造成分布不均匀了,

增加了碰撞的几率,

减慢了查询的效率,

造成空间的浪费。 

HashMap在1.7和1.8做的比较大的一个改变

    • JDK1.7是数组+链表,用的头插法——导致链表成环——如何成环的分析一下。

    • JDK1.8是数组+链表+红黑树,用尾插法来避免逆序。

    • JDK1.7直接用hash值和需要扩容的二进制数进行&与操作

    • JDK1.8沿用了JDK1.7的计算规律,也就是扩容前的位置+扩容的大小值=JDK1.8的计算方式,而不再是JDK1.7那种异或的操作,只需要判断hash值的新增参与运算的位是0还是1就可以了

扩容后JDK1.7

1.7之前使用的是数组加链表,它的数据节点是一个Entry节点,就是它的一个内部类,1.7之前数据插入的过程是使用头插法,但是HashMap使用头插法,它在扩容的一个过程,里面有一个resize方法,他又调用了一个transfer的方法,把里面的一些entry进行了一个rehash,在这个过程当中,可能会造成一个链表的环,就可能在下一次get的时候出现一个死循环的情况,其次它也没有加锁,在多线程并发的情况下,不能保证数据的安全性。

在多个线程并发扩容时,会在执行transfer()方法转移键值对时,造成链表成环,导致程序在执行get操作时形成死循环

 

JDK1.8进行了改变,改为数组+链表+红黑树,把原来的一个Entry节点变成了一个Node节点,它整个put过程也做了一个优化。

参考

https://blog.csdn.net/u012156116/article/details/81206649

https://blog.csdn.net/u010890358/article/details/80496144

https://www.cnblogs.com/LiaHon/p/11149644.html

https://blog.csdn.net/gududedabai/article/details/85784161

https://www.cnblogs.com/williamjie/p/9358291.html

weixin151云匹面粉直供微信小程序+springboot后端毕业源码案例设计 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值