HashMap的扩容机制、初始化容量大小的选择、容量为什么是2的次幂

前置知识

先来看看HashMap中的成员属性

解释:

  • size当前的容器中Entry的数量,也就是当前K-V的数量
  • loadFactory装载因子,用来衡量HashMap满的程度,loadFactory的默认值是0.75
  • threshold临界值,当实际KV数量超过threshold时,就会触发扩容机制
    threshold = capatity * loadFactory

容量capatity

除了以上这些成员属性外,还有一个紧密关联的概念:capatity容量。
capatity与size不是同一个概念,size来表示当前的HashMap已经装了多少,capatity是容量,HashMap最多能装多少

利用反射获取HashMap的容量capatity的大小
看好这个方法,接下来要用

public static void printHashMapCapacity(HashMap map) throws Exception {  
    Class<? extends HashMap> mapClass = map.getClass();  
    Method capacity = mapClass.getDeclaredMethod("capacity");  
    capacity.setAccessible(true);  
    Integer invoke = (Integer) capacity.invoke(map);  
    System.out.println(invoke);  
}

如果利用HashMap的无参构造,默认情况下的容量是16,当达到扩容机制时,就会扩容到32,以此类推
如果在new时,指定容量大小,则HashMap会选择第一个大于等于此数字的2的幂次作为初始容量

HashMap<Object, Object> hashMap = new HashMap<>();  
printHashMapCapacity(hashMap);  
// 默认的容量16  
  
HashMap<Object, Object> map2 = new HashMap<>(1);  
printHashMapCapacity(map2);  
// 1 , 因为2的0次方是1  
  
HashMap<Object, Object> map3 = new HashMap<>(7);  
printHashMapCapacity(map3);  
// 8 , 第一个大于7的2的次幂是8

阿里Java开发手册中规定,在初始化HashMap时,应该尽量指定其初始大小

loadFactory和threshold

这两个属性用来指定HashMap的扩容机制
threshold = loadFactory * capacity
当HashMap中的size超过threshold时,就会触发扩容,每次扩容后的容量是原始容量的2倍
从默认的初始容量16扩容到32、64、128…
loadFactory是装载因子,用来衡量HashMap满的程度,默认值是0.75f。
设置成0.75的好处是:capacity是2的次幂,两个数的乘积还是整数,避免浮点数造成影响。
我们初始化HashMap时,除了可以指定初始化容量大小外,还可以指定装载因子的大小的,但是修改装载因子的值是不推荐的。

为什么要设置初始化容量

当HashMap 发生扩容时,会新建一个指定容量的桶,然后将原来的Entry拷贝到新的桶中。
此过程会有较大的资源消耗。
为了避免在向HashMap中put过多元素时频繁发生扩容,需要指定一个合适大小的初始化容量。

初始化容量设置多少合适

我们的目的是避免频繁发生扩容,所以一次性指定初始化容量合适大小。
并不是我要塞入多少个元素,就初始化为多少,来看一个例子:
当我要向HashMap中put 7个元素,我将HashMap初始化为7

HashMap map = new HashMap(7);

因为传入的是一个7,HashMap会选择一个大于等于该值的2的次幂作为容量capacity
根据loadFactory=0.75,计算出threshold = 8 * 0.75 = 6
当我们put第7个元素时,就会触发扩容,扩容到16。
但是我们就想put 7个元素,在第7个时触发扩容,导致了性能和空间的浪费。
所以,我们在初始化时,选择多少的初始化容量,值得考虑。

借鉴HashMap 中putAll()方法,得出以下公式:

initCapatity = expectedSize / 0.75F + 1.0F 

这个计算方法虽然浪费了一些空间,但是在性能上却提升了。
当我们要put 7个元素, initCapacity = 7 / 0.75 + 1 = 10 ,经过HashMap的计算,第一个大于10的2的次幂的数是16,所以HashMap的容量是16。
16 * 0.75 = 12 ,当我要put第7个元素时,并不会发生扩容,性能得到提升。
而且与第一次我们设置初始化容量为7相比,最终所占用的空间是一样的。

为什么选择2的次幂作为容量

因为,在put时,HashMap会根据key的hash来计算对应Entry所在桶的位置。
通常都是利用求余来实现的

index = hash % capacity

但是直接利用%来计算是非常慢的,效率非常第低。
我们知道,在计算机中,最快的运算就是位运算了。
存在这样一个规律:当capacity是2的次幂时,满足以下恒等式

hash % capacity == hash & (capacity - 1)

在我们创建HashMap时,就做了第一步处理,选择大于等于给定数字的第一个2的次幂作为容量,就保证了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值