# 问题

16.HashMap 为什么默认负载因子是 0.75?如何选择合适的负载因子?

# 标准答案

HashMap 的默认负载因子 0.75 是时间和空间的折中选择,它保证了哈希桶的填充程度不会太高(避免过多哈希冲突),同时也不会太低(避免浪费内存)。选择合适的负载因子需要权衡查找效率、空间占用和扩容成本,通常:

  • 较小的负载因子(如 0.5) 适用于高性能查询场景,减少冲突但增加内存占用。
  • 较大的负载因子(如 0.9) 适用于节省空间的场景,但可能增加冲突,提高 get()put() 的时间成本。

# 答案解析

HashMap 采用数组 + 链表(JDK 1.8 以后可能是红黑树)存储数据,负载因子(Load Factor)控制着何时触发扩容,计算方式如下:

if (size >= capacity * loadFactor) {
    resize();
}
1
2
3

默认负载因子 0.75 代表数组填充 75% 时触发扩容。选择 0.75 主要基于哈希碰撞概率与空间利用率的平衡

  1. 哈希冲突概率分析

    • 负载因子越高,桶存储的元素越多,哈希冲突概率上升,导致链表变长或红黑树存储更多元素,get()put() 操作性能下降。
    • 负载因子越低,哈希桶填充率下降,减少冲突,但会浪费更多内存。
    • 研究表明,0.75 能较好地控制链表长度在 1~2 之间,查找时间复杂度趋近 O(1)。
  2. 时间 vs. 空间权衡

    • 0.75 确保平均查找成本较低,同时避免频繁扩容导致的内存和计算开销。
    • 过小的负载因子(如 0.5)会导致更频繁的扩容,而扩容涉及数据重新哈希(Rehashing),开销较大。
  3. 扩容机制的影响

    • HashMap 每次扩容时,容量翻倍(capacity = capacity * 2),通过 capacity & (newCap - 1) 计算新索引,减少了 rehashing 成本。
    • 0.75 作为负载因子确保扩容不会过于频繁,同时保证 HashMap 的 get() 仍接近 O(1)。

# 常见问题

  1. 为什么不是 0.5?
    0.5 会让 HashMap 更快扩容,导致更高的 rehashing 开销,影响 put() 性能。

  2. 为什么不是 1.0?
    1.0 代表 HashMap 填充满了才扩容,哈希冲突极高,退化成链表或红黑树,get()put() 可能变为 O(n) 或 O(log n)。

  3. 能否修改负载因子?
    可以通过 new HashMap<>(initialCapacity, loadFactor) 设置,但通常不推荐调整,除非对查询性能或空间有特殊要求。

# 最佳实践

  1. 高性能查询场景(如缓存),推荐 loadFactor = 0.5,减少哈希冲突,提高 get() 性能。
  2. 大数据存储但不频繁查询的场景,可适当增加负载因子(如 0.8~0.9),节省内存但允许更多哈希冲突。
  3. 避免盲目修改负载因子,除非经过性能测试验证适合业务场景,否则 0.75 仍是最佳选择。

# 深入追问

  • 负载因子如何影响 ConcurrentHashMap?并发环境是否适合 0.75?
  • HashMap 何时会退化成链表?如何避免退化?
  • 负载因子对 GC(垃圾回收)有何影响?

# 相关面试题

  • HashMap 为什么要扩容?扩容的性能影响是什么?
  • HashMap 的 rehashing 过程是如何优化的?
  • HashMap 和 ConcurrentHashMap 在负载因子上的策略是否一致?