HashMap 的初始化问题

发布时间 2023-09-16 18:16:33作者: YaosGHC

最近的两次面试被分别被问到了:

如果初始化 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 为参数的,另外三个分别是:

  • 无参
  • 指定容量
  • 指定容量和负载因子

而事实上这三个方法中都并没有去创建实际的桶数组,而只是指定了两个参数:负载因子loadFactorthreshold

threshold表示数组被初始化之前可以容纳的元素数量的阈值

那么具体的创建又是在哪里实现的呢?

put()函数

以初始化指定容量 20 为例:new HashMap(20),在构造方法中会指定两个属性:

  1. 负载因子loadFactor:未指定则为默认值0.75
  2. 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();