最近的两次面试被分别被问到了:
如果初始化 HashMap 的容量为 100,那么实际容量会是多少?
如果初始化 HashMap 的容量为 20,那么实际容量会是多少?会不会发生扩容?
自己想当然的会回答:容量会是满足 2 的幂次 * 负载因子 >= 初始化指定容量的值
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>(100);
System.out.println(map.size());// 0
map.put("1",2);
System.out.println(map.size());// 1
}
但是这好像是彻底错误的,于是去检查源码探究这个问题
构造函数
当使用构造函数创建 HashMap 时,发现 4 个构造函数中,抛开一个以 Map 为参数的,另外三个分别是:
- 无参
- 指定容量
- 指定容量和负载因子
而事实上这三个方法中都并没有去创建实际的桶数组,而只是指定了两个参数:负载因子loadFactor
和 threshold
threshold
表示数组被初始化之前可以容纳的元素数量的阈值
那么具体的创建又是在哪里实现的呢?
put()
函数
以初始化指定容量 20 为例:new HashMap(20)
,在构造方法中会指定两个属性:
- 负载因子
loadFactor
:未指定则为默认值0.75
threshold
为大于 20 的最小的 2 的幂数,也就是32
但是此时,这个数组是没有被创建的,也就是说实际的桶数组是null
不存在的
只有当向 map 中put()
元素的时候,才回去检查数组是否为null
并初始化
在put()
方法的实际实现putVal()
方法中
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
判断到数组为空,会有第一次扩容
因为此时旧容量为 0,新容量会被指定为旧 threshold
if (oldCap > 0) {}
else if (oldThr > 0) newCap = oldThr;
这里还回去计算新的threshold
,在不超过最大容量的情况下,会变成 newCap*loadFactor
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
并最终将这个数组创建出来,这个返回的底层实际数组的大小为 32
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
后续put()
过程中发生的扩容参考则变成了threshold
if (++size > threshold) resize();
标签:初始化,newCap,HashMap,容量,问题,数组,threshold
From: https://www.cnblogs.com/yaocy/p/17706595.html