HashMap原理详解,HashMap源码解析

HashMap是一个数组链表和红黑树(想了解红黑树可以看我的另一篇文章红黑树详解)的结合体 HashMap的第一层表现是数组,HashMap默认创建一个长度为十六的数组来储存数据,但不同的是,它并非是先放在第0个索引,然后第一个索引那么放置,而是通过key获取对应的32位hash值,然后高十六位和低十六位进行异或操作,然后用结果的后四位和15的二进制(也就是1111)进行与操作,得到一个0-15之间的值来匹配索引位置。

当匹配到的索引位置已经有值时,他会和当前索引中保存的所有key通过equal方法进行判断是否相等,如果相等则进行覆盖,不相等时,则保存在最后一个值的next属性中,作为链表保存。

当一个链表过长时,注定会影响查询性能,此时则需要一个能够平衡性能的数据结构进行保存,当链表长度大于8时,链表变为被转换为红黑树保存,同理,当数据被删除的过少时,也会被转换为链表,不过不是8,而是6,当数据量为六个时,红黑树又会被转化为链表。这相当于在两个变换过程中加了一个缓冲区,防止因为删除和新增操作造成的频繁变换,造成性能损耗。

HashMap默认创建十六长度的数组,我们也可以自定义长度,不过整个长度必须是2的n次幂,因为存放索引是通过二进制的异或操作结果确定的,如果数组长度不是二进制,那么会导致部分长度无法访问,或者是部分长度根本不存在的问题(当我们调用HashMap的构造方法时,如果不传入2的n次幂的整数,他则会寻找最近的值作为HashMap的初始长度)

HashMap也会进行扩容,当数组容量超过负载因子的百分比时(默认是百分之七十五,可以创建HashMap时自己设置),HashMap会进行容量乘二的扩容,但因为容量的变化,所以各个hash值的索引也会改变,所有元素都需要重新存储(即使没有这个原因,也要重新存储,因为数组是一个连续的内存,想要扩容数组只有一边办法,那就是找到更长的连续内存,创建一个新数组)。

了解完HashMap后实际上会有一些疑问,比如说,他在处理索引时会进行复杂的操作,他会通过扰动函数(也就是说之前说的用hashcode的高十六位和第十六位进行异或操作)得到一个新的值,在将新的值的后四位和15的二级制进行与操作,这一步的意义是什么呢?我为什么不直接取hash值的后四位呢?官方解释是让其分布更平均,那为什么他生成的hash值的后四位就不平均呢?

猜测可能有两种可能,一种是hash值的生成本身就有缺陷,后四位可能更倾向于某个位置,第二种可能是hash值的生成和key有着比较规律性的关联,而在一个项目中的key通常又很相似,所以会造成最终的hash值后四位比较相似。

HashMap源码

首先HashMap的put方法会调用hashcode函数来获取hash值

我们进入hash方法,如下

这一步是在将获取的hashcode的高位和低位进行异或运算,这也是我们上面说的扰动函数,其中hashCode方法就是在获取hash值,我们进入hashCode

可以看到这个方法没有做实现,首先这绝对不是一个接口,没有实现是因为hashCode方法的是实现是内置在JVM中的c++语言实现的,所以在java代码中看不到。当获取完hash值后,我们便可以进入第一步的putVal方法中进行添加key和value了。

其中Node类就是我们保存的一个值,而tab是Node的数组,也可以知道他就是最终保存数据的结构,其中Node类内容如下

可以看到Node的属性包括hash值,key,value,以及next用来保存下一位节点的引用

了解了Node的结构后,在看回putVal的代码

当数据量超过负载因子规定时则调用resize方法

HashMap源码中的红黑树转换以及新增删除逻辑极为复杂,这里就不说了,想了解更多的红黑树知识,可以看我的另一篇文章,红黑树详解

  • 25
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不止会JS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值