要让hashmap一次性装下预估数据且不扩容,需按负载因子0.75反推最小容量:理论容量为⌈n/0.75⌉,再对齐到2的幂(如n=800→1067→2048);直接传n(如new hashmap(800))会导致实际容量1024,仅支持768个元素,第769次put即触发扩容。

要让 HashMap 一次性装下预估数据、不扩容,关键不是“给多少元素就设多少容量”,而是按负载因子反推理论最小容量,再对齐到 2 的幂。
核心公式必须带负载因子
默认负载因子是 0.75,这意味着:实际能安全存下的元素数 = 初始容量 × 0.75。所以反过来,想存 n 个元素,理论最小容量是 n / 0.75,再向上取整到最接近的 2 的幂。
- 计算步骤:先算
(int) Math.ceil(n / 0.75),得到理论下限(比如 n=800 → 800/0.75≈1066.67 → 向上取整为 1067) - 再用
tableSizeFor(1067)得到实际容量(JDK 内置方法,结果是 2048) - 等效写法:
new HashMap(2048, 0.75f)或直接new HashMap((int) Math.ceil(800 / 0.75))
别踩“传多少就用多少”的坑
写 new HashMap(800) 看似直觉,但 HashMap 会把它喂给 tableSizeFor(),结果是 1024 —— 这个容量在 0.75 负载下最多存 768 个元素。第 769 次 put 就触发扩容,所有已有数据重哈希,白耗 CPU 和内存。
- 错误示例:
new HashMap(1000)→ 实际容量 1024 → 安全上限 768 - 正确示例:
new HashMap((int) Math.ceil(1000 / 0.75))→ 传 1334 →tableSizeFor(1334)=2048 - 注意:
Math.ceil()返回 double,必须强转int,否则编译失败
批量构建时更推荐流式写法
从 List 转 Map 是高频场景,循环中逐个 put 极易触发多次扩容。预分配虽好,但 Java 10+ 的 Collectors.toMap 更省心,底层已做容量预估。
- 手动预分配:
new HashMap(list.size() + 16)(+16 是缓冲,防后续额外插入) - 推荐写法:
list.stream().collect(Collectors.toMap(Item::id, Function.identity())) - 如果 key 可能重复,改用
toMap(keyMapper, valueMapper, (v1,v2)->v1)处理冲突
负载因子可以调,但得看场景
0.75 是通用平衡点,不是铁律。读多写少、内存宽松?可设 0.85~0.9;写入不均、GC 敏感?可降到 0.6~0.7。
- 设 0.9:省内存,但 get/put 平均链长上升,只建议用于只读且 key 分布极均匀的缓存表
- 设 0.6:桶更稀疏,冲突减少,适合 key 哈希值劣质或对延迟敏感的场景
- 调完负载因子,初始容量公式仍为:
tableSizeFor((int) Math.ceil(n / loadFactor))











