HashMap的默认长度为什么是16?其他数字行不行?

之前在网上看到的一个面试题,觉得挺有意思的,找了相关资料,看了HashMap的源码,今天有时间,写上来。(JDK版本1.9,其他版本的源码可能有出入)

首先来看HashMap中的一段代码
在这里插入图片描述
注释就说的很清楚了,默认的初始容量 - 必须是2的幂。也就是说,HashMap的长度自己定义的时候,只要是2的次幂就行。那么为什么要是2的次幂?3的次幂行不行?我们接着往下看。

来看HashMap的put方法:
在这里插入图片描述
可以看到put方法调用了putVal方法,再来看putVal方法,以下为putVal方法的部分源码:
在这里插入图片描述
红圈标注的就是在计算存储的值要存放在tab数组的位置,也就是这段算法决定了为什么HashMap的长度要是2的次幂。

n是什么?
我们不妨先来看这段代码中的resize方法做了什么在这里插入图片描述

下面是resize方法的部分源码,从注释就可以看出,是用来初始化或扩容数组的在这里插入图片描述
那么显而易见,tab就是HashMap底层存储数据的数组。n就是tab数组的长度。

hash是什么?

put方法在调用putVal方法的时候,传参数时,调用了hash方法,下面是hash方法的源码
在这里插入图片描述
hash方法计算了key的hash值。

ok,所有变量的用途都搞清楚了,我们还是接着来看这段代码:
在这里插入图片描述
我们假设数组长度分别为15和16(也就是n=15 或 n=16),假设两个值的hash码分别为8和9,那么&运算后的结果如下:在这里插入图片描述
从上面的例子中可以看出:当它们和15-1(1110)“与”运算的时候,产生了相同的结果,也就是说它们会定位到数组中的同一个位置上去,这就产生了哈希碰撞,hash码是8和9的值,被放到数组中的同一个位置上形成链表,那么查询的时候就需要遍历这个链表,得到hash码是8或9的值,这样就降低了查询的效率。
同时,我们也可以发现,当数组长度为15的时候,hash值会与15-1(1110)进行“与”运算,15-1(1110)永远是0,想0001,0011,0101,1001,1011…,这些位置永远都存放不了元素,空间浪费相当大,更严重的是,数组可以使用的位置比数组实际长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!

而当数组长度为16时,即为2的n次方时,2n-1得到的二进制数的每个位上的值都为1,这使得在低位上&时,得到的和原hash的低位相同,加之hash()方法对key的hashCode的优化,使得只有相同的hash值的两个值才会被放到数组中的同一个位置上形成链表。
所以说,当数组长度为2的n次幂的时候,不同的key算得得index相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。

可能有人想问,那我自己定义HashMap的长度呢?我把长度写成其他数字,比如这样:在这里插入图片描述
不用担心,你能想到的,官方也想到了。我们来看HashMap的构造方法:
在这里插入图片描述
可以看出它调用了这个构造:
参数一:你定义的长度
参数二:加载因子,默认是0.75
在这里插入图片描述
黄色方框不用多说,当你定义的值超过HashMap的最大容量时,使用最大容量

红色方框,我们来看tableSizeFor(int cap)方法,做了什么,下面是该方法的源码:
在这里插入图片描述

我们可以运行这段代码,可以发现当你传的9进来后,会帮你优化为16在这里插入图片描述
继续运行这段代码,会发现如果传3会优化为4,传5会优化为8,以此类推,都会优化为2的次幂。
传负数、0、1,会被优化为1。

ok,以上

HashMap和ConcurrentHashMap的区别有以下几点: 1. 线程安全性:HashMap是非线程安全的,因为在多线程环境下,多个线程可能同时对HashMap进行写操作导致数据错误。而ConcurrentHashMap是线程安全的,内部通过分段锁的机制保证了多线程环境下的安全性。 2. 效率:在单线程环境下,HashMap的效率要优于ConcurrentHashMap。因为ConcurrentHashMap需要做额外的安全措施,例如锁的申请和释放等操作,会带来额外的开销。而在多线程环境下,ConcurrentHashMap由于采用了分段锁的机制,可以进行高效的并发访问,所以效率会优于HashMap。 3. 迭代器:HashMap的迭代器是fail-fast的,即如果在迭代过程中对HashMap进行写操作,会直接抛出ConcurrentModificationException异常。而ConcurrentHashMap的迭代器是弱一致性的,即当迭代的过程中对ConcurrentHashMap进行修改,迭代器仅仅是不保证是否看到了修改,但是不会抛出ConcurrentModificationException异常。 4. 初始容量和负载因子:HashMap的初始容量为16,负载因子为0.75。而ConcurrentHashMap可以通过设置segment水平的并发度来提高并发访问能力,其默认的segment大小为16,而每个segment的初始化容量和负载因子也是由用户可设置的。 5. 线程安全级别:ConcurrentHashMap提供了多种不同的线程安全级别,可以根据具体业务场景选择不同的级别进行使用。常用的级别包括:完全并发(最高级别)、读写分离(默认级别)、只读等级(最低级别)。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值