HashMap底层原理及hashmap扩容源码详细解读(JDK1.8)


前言

hashmap底层原理及扩容机制一直是java核心知识之一。其源码相当复杂程度精妙复杂,,因而hashmap源码也被称做最值得学习的源码。

一、hashmap的底层原理和扩容机制是什么

hashmap由数组链表加红黑树组成,每添加一个键值对,计算键的hash值,将获取到的值高16位和低16位进行异或处理,得到的值与数组长度减一进行按位与运算,获取到的最终值即该元素所在数组位置,若该位置有元素,即存在hash冲突,采用链式存储法存储该元素,当链表长度超过8时,链表转为红黑树以增加其查询效率。当hashmap当前所占数组数量超过hashmap容量乘以装载因子时,且其新加的元素出现了hash冲突,触发扩容机制。扩容长度为两倍扩容。将每个键的hash值与新的数组长度减一进行按位与运算,所得的值如果与原位值一样,则放于原位置,反之则放于原位置加旧容量。后续会详细说明每一步的作用。下面进行hashmap源码解读。

二、源码解析

1.新建hashmap调用put方法

在这里插入图片描述

2.点击进入put方法

在这里插入图片描述
onlyifAbsent表示是否覆盖,默认是覆盖,evict是为linkedhashmap服务,这里用不到。hash(key)即计算key的hash值。
`

3.点击进入hash(key)方法

在这里插入图片描述
如果key是空则返回零,非空则调用object类中hashcode方法,与自身逻辑右移16位(高位补零)进行异或运算。这里的形参key是object型,采用父类应用指向子类对象,即多态方式创建对象。如果子类重写过hashcode方法,则会调用子类覆盖重写后的方法。
前三部主要在干一件事,调用key的hashcode,高低位异或运算后得到最终hash值。

4.点击进入核心源码putval方法

4.1hashmap初始化

putval方法包含了hashmap底层原理和扩容机制。该方法的源码很长,这里逐个代码块进行分析
在这里插入图片描述
先看局部变量tab,[]对应hashmap底层组件之一数组,Node<K,V>有个next属性指向下一个Node<K,V>,故Node<K,V>对应hashmap底层组件之一的链表。第一个if语句表示第一次调用put方法时,对hashmap进行初始化。
第二个if语句表示将key的hash值和tab数组长度减1进行按位与运算。得到的最终值即对应在tab数组上的位置,如果这个位置没元素,则在该位置放入node节点,node存储的即传入的键值对。

4.2处理hashmap冲突

从源码第631行else开始接下来均为处理hashmap冲突
在这里插入图片描述
第633行到第635行表示当这个数组位已经存在元素,且新加的元素key hashcode和equals方法完全一样,直接替代。
第636到637行表示当新加入的元素是红黑树的一个实例,则将该节点挂载在该红黑树下。
第638行表示当挂载在数组该位置下的是个链表的时候,进行如下处理。
640到644行表示,如果链表中的每个元素的键都与新加的元素的键不同,则将新加的元素挂载在链表末端,如果链表长度大于等于7,将链表转换为红黑树。
646到648行表示如果链表下的某个元素的键与新加元素的键完全一样,跳出循环。
652到655行触发条件是程序通过第648行跳出循环,表示key值重复,则新加的value值取代原key值对应的value值。
660到第662行表示触发扩容。

三、 hashmap问题详解

问题一.为什么要对hashcode方法获得的值进行异或处理。

用异或处理是为了保证在length较小的时候,高位也能参与hash值的计算,用异或hash值中有任何一位改变得到的值就会改变。同时,调用hashcode方法所获得的32位数字不具备数学意义上的随机性,高16位低16位的异或处理后,最终的16位数字每位0或1的概率都是百分之50。

问题二.为什么落于数组的位置,取的的是异或处理获得的值和数组长度-1进行按位与运算,且数组长度必须是二的幂次方。

数组长度为2的幂并且按位与运算是为了保证数据均匀分布在数组上,因为二进制中2的幂次方减一所有位数均为1,按位与运算可保证获得的值位于0到数组长度减一之间。且得到的二进制值每一位0或1的概率都是百分之50,固数据落在每个格子的概率都是一样的。若容量不是2的幂数组中某个格会出现一直为空的情况,即便设置的不是2的倍数,java也会将容量更改为距离最近的2的幂。若不采用异或运算,会导致最终值超出数组界线。

问题三.JDK1.8对hashmap扩容做了什么优化

jdk1.8将hashcode方法高低16位异或运算所获取的值与新的数组长度减一进行按照位与运算,新的数组与原数组在二进制中变化是新数组比原数组多了个1,所以按位与运算后只有首位有可能发生变化,且首位发生变化的概率是百分之五十,因为原hash值是异或运算得出,每一位是零或一的概率是百分之五十),如果与原位置一样,则放在原位置,如果与原位置不一样,再放于原位置加旧容量。这样做的目的是让元素均匀分布在新数组中,因为首位变化率是百分之50,固原位置数据是否不变的概率是百分之50,而原位置加旧容量处是否有数据的概率也是百分之50。

问题四.为什么两倍扩容

是为了保证数组长度是二的幂次方。至于为什么是二的幂次方,已在问题二中详细解答。

问题五.为什么默认装载因子是0.75

装载因子太大链表过多查询效率低,太小数组利用率低,数据分散。0.75符合数学泊松分布。

四、总结

综上所述,hashmap计算元素所处位置的方式,以及扩容原理,其本质就是保证数据均匀的落在数组上。在巧妙的异或、按位与运算及扩容机制,最终尽可能得保证了数据落在数组的每个位置的概率是一样的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值