使用 HashMap 存一万条数据,构造时传 10000 还会触发扩容吗?

本文解析了HashMap构造方法中initialCapacity参数的实际意义,通过源码分析解释了为何即使初始化为10000,在存储同样数量的数据时也不会触发扩容。并给出了初始化HashMap时选择initialCapacity的最佳实践。

问题

  向 HashMap 中存 10000 条数据,初始化时,构造方法传值 10000,会触发扩容吗?

Map<String,String> map = new HashMap<>(10000);

分析

乍一看

  肯定会触发扩容呀,因为 HashMap 中有个负载因子默认为 0.75,就是说存储的数量超过容量的 75% 就会触发扩容,put 到后 25% 的数据时,肯定就会触发扩容。但事实真是这样吗?源码中有我们想知道的一切,真相只有一个。

分析源码

HashMap 的初始化

  在 HashMap 中,提供了一个指定初始容量的构造方法 HashMap(int initialCapacity),这个方法再通过 DEFAULT_LOAD_FACTOR 调用 HashMap 另一个构造方法,初始化 loadFactor 为 0.75。

public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity, float loadFactor) {
  	......
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}

  构造方法初始化了两个成员变量 threshold 和 loadFactor,其中 threshold 就是用来存储触发 HashMap 扩容的阈值,也就是说,当 HashMap 存储的数据量达到 threshold 时,就会触发扩容。
  从改造方法中可以看出,threshold 并没有直接使用传入的 initialCapacity 作为扩容阈值,而是通过 tableSizeFor 方法处理后再赋值给 threshold。

static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

  这啥呀?看不懂也没关系,我来解释。tableSizeFor 的作用就是寻找大于 cap 的 2 的整数次方,例如如果传入 10,经过 tableSizeFor 处理后返回 16。至于为什么这样做,不在此处详细讨论。

回归正题

  还有一个问题,一直在说 threshold 是触发扩容的阈值,即 (initialCapacity * loadFactor),但是我们再构造方法中使用 tableSizeFor 初始化 threshold,并没有用到 loadFactor,其实这一步被移交给 resize 方法实现了,因为我们并没有在构造方法中初始化哈希表数组,扩容阈值 threshold = initialCapacity * loadFactor,这一步在 resize 中完成。
  如果我们从外部传递进来 10000 初始化 initialCapacity ,实际上经过 tableSizeFor 方法处理之后,最终 threshold 就会变成 2 的 14 次幂 16384,再在 resize 方法中乘上负载因子 0.75,实际在不触发扩容的前提下,可存储的数据容量是 12288(16384 * 0.75),用来存放 10000 条数据,绰绰有余,所以并不会触发扩容。

initialCapacity

  关于如何选择 initialCapacity,我们看看阿里巴巴 Java 开发规范,规范要求在初始化 HashMap 时,必须指定 initialCapacity,因为这样可以减少 resize 次数,提高程序效率。因为 threshold = initialCapacity * loadFactor,所以 initialCapacity = (需要存储元素个数 / loadFactor) + 1。
在这里插入图片描述

示例

  想要使用 HashMap 存放 10000 条数据,应该设置 initialCapacity = 10000 / 0.75 + 1 = 13334,然后哈希表容量会被 tableSizeFor 方法调整到 16384(2^14),threshold = 16384 * 0.75 = 12288 足够存储 10000 条数据而不会触发扩容。

总结

  • HashMap 构造方法传递的 initialCapacity 实际表示哈希表的容量,不代表扩容阈值。
  • 构造方法传递的 initialCapacity,会被 tableSizeFor 方法调整为大于它的 2 的整数次方。
  • 如果使用 initialCapacity 进行初始化,HashMap 是否扩容,由 threshold 决定,扩容阈值 threshold = initialCapacity * loadFactor。
  • 在初始化 HashMap 时,必须指定 initialCapacity 来提升效率,initialCapacity = (需要存储元素个数 / loadFactor(默认0.75)) + 1。
<think>我们正在讨论HashMap的自动扩容机制。根据用户的问题,我们需要详细说明触发条件和扩容倍数。同,根据引用内容,我们可以总结如下: 触发条件:当HashMap中的元素数量(即size)超过阈值(threshold),就会触发扩容扩容倍数:每次扩容容量(capacity)会变为原来的2倍。 详细说明: 1. 阈值(threshold)的计算:阈值 = 当前容量(capacity) * 负载因子(loadFactor) 默认负载因子为0.75,默认初始容量为16(引用[4])。 因此,默认情况下,当元素数量超过16*0.75=12,就会触发扩容。 2. 扩容过程:创建一个新的数组,大小为原数组的2倍(引用[3]提到“扩容后的哈希表将具有两倍的原容量”),然后重新计算每个元素在新数组中的位置(rehash)。 3. 容量上限:HashMap的最大容量为1<<30(即1073741824),超过这个值则不再扩容,阈值设置为Integer.MAX_VALUE(引用[2])。 4. 初始容量处理:HashMap在初始化,会保证容量是2的幂次方(引用[3])。如果构造入初始容量HashMap会将其调整为大于等于该值的最小的2的幂次方。 因此,我们可以这样组织回答: 回答结构: 1. 触发条件:当HashMap中元素的数量(size)超过当前阈值(threshold)触发扩容。 2. 阈值计算:threshold = capacity * loadFactor(默认负载因子0.75)。 3. 扩容倍数:每次扩容,新容量为原容量的2倍(直到达到最大容量限制)。 4. 补充说明:初始容量和负载因子可以自定义,但容量总是2的幂次方。 同,我们需要在回答中引用相关引用,并在末尾生成相关问题。 注意:用户的问题是关于HashMap的,所以我们要围绕这个主题。 </think> ### HashMap自动扩容机制详解 #### 一、触发条件 HashMap扩容触发由**阈值(threshold)** 控制,其计算规则为: $$ \text{阈值} = \text{当前容量} \times \text{负载因子} $$ 当HashMap储的键值对数量**超过阈值**,自动触发扩容[^3][^4]。例如: - 默认容量16,负载因子0.75 → 阈值$16 \times 0.75 = 12$ - 当第13个元素插入触发扩容 #### 二、扩容倍数 每次扩容后,新容量为**原容量的2倍**: $$ \text{新容量} = 2 \times \text{旧容量} $$ 例如: - 初始容量16 → 首次扩容后32 - 再次扩容后64(以此类推)[^3] #### 三、关键特性 1. **容量限制** - 最大容量为$1<<30$(1073741824),超过后阈值设为`Integer.MAX_VALUE`,不再扩容[^2] - 初始容量自动对齐为**最近的2的幂次方**(如入15 → 实际分配16)[^4] 2. **扩容过程** ```mermaid graph LR A[检查size > threshold] --> B[创建新数组 size=2*旧容量] B --> C[遍历旧数组所有元素] C --> D[重新计算hash & 索引位置] D --> E[迁移到新数组] ``` 3. **性能影响** - 扩容需**重建哈希表**(rehash),间复杂度$O(n)$ - 负载因子权衡: - **0.75**:空间与间平衡点(Java默认值) - **增大**→减少扩容次数但增加哈希冲突 - **减小**→增加空间开销但提升查询效率 > **最佳实践**:预估算数据量,构造指定初始容量: > `new HashMap<>(1000)` 避免频繁扩容[^4] --- **
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值