1. 首页
  2. 面试专题
  3. 文章列表
多家公司 Java 后端 HashMap 与集合源码 2026-06-14

HashMap 扩容被追问时,别停在数组加链表

HashMap 题的重点不是背结构,而是能解释扩容为什么发生、碰撞如何处理、树化有什么边界,以及为什么它不能被当成并发容器。

HashMap 是 Java 面试里最容易被问到“你懂源码吗”的题。很多回答会停在“数组加链表,Java 8 后链表会转红黑树”,但面试官继续追问 put 过程、扩容时机、负载因子、并发风险时,真正的理解差距就会出现。

这道题不应该被答成源码名词背诵。更好的思路是先说清 HashMap 解决什么问题:用 hash 把 key 分散到数组桶里,用链表或树处理冲突,用扩容控制单个桶过长带来的性能退化。

put 的主线其实很短

一次 put 大致要经过几步:先计算 key 的 hash,再根据数组长度定位桶;如果桶为空,直接放入;如果桶里已有节点,就比较 hash 和 key 是否相同,命中则覆盖 value,未命中则挂到链表或树结构上。这里的关键不是背每一行源码,而是知道 HashMap 在尽量把“查找一个 key”压到接近常数时间。

为什么还要做 hash 扰动?因为数组下标通常由低位参与计算,如果 key 的 hash 高位有差异、低位相似,就容易堆到同一个桶。扰动函数把高位信息混到低位里,是为了让分布更均匀。

扩容不是空间不够,而是冲突成本变高

HashMap 默认负载因子是 0.75,容量乘以负载因子得到阈值。元素数量超过阈值后触发扩容,数组长度通常翻倍。翻倍的好处是旧节点要么留在原桶,要么移动到原索引加旧容量的位置,重新分布成本比完全重算更可控。

这也是面试里常见的项目延伸:如果你知道一个 Map 大概会放多少数据,初始化容量就不是微优化。频繁扩容会带来数组分配和节点迁移,尤其在批量导入、缓存构建、热点配置加载这类场景里,提前估算容量能减少抖动。

红黑树不是万能优化

链表过长后,HashMap 会考虑树化,但并不是链表一长就直接树化。数组容量太小时,更倾向于扩容,因为冲突可能只是数组太小导致的;容量足够大且桶内节点达到阈值,才更适合转成红黑树。

这个设计说明了一个常识:优化要先判断瓶颈。桶里冲突多,可能是容量小,也可能是 key 的 hash 分布差,还可能是被恶意构造。只说“转红黑树后就是 O(log n)”不够,因为正常业务里更希望冲突不要严重到需要依赖树。

并发边界要说清楚

HashMap 不是线程安全容器。多个线程同时写入时,可能出现数据覆盖、结构不一致,旧版本实现里还可能出现扩容链表问题。现代 Java 里不要再把这题答成“多线程一定死循环”,更准确的说法是:HashMap 没有为并发写提供一致性保证,不能作为共享可变状态直接使用。

如果需要并发访问,要看场景选择 ConcurrentHashMap、外层同步、不可变快照,或者干脆避免多线程共享写。面试官真正想听的不是“我知道 ConcurrentHashMap”,而是你能否根据读多写少、写多读少、是否需要强一致遍历、是否能接受最终一致来选方案。

把源码题落到工程里

项目里遇到集合性能问题,排查顺序可以很朴素:数据量有多大,key 的分布是否正常,是否频繁扩容,是否在并发写,是否把大对象长期挂在 Map 上导致内存回收困难。

能把 HashMap 从源码结构讲到工程取舍,回答就会自然很多。它不是一道“背数组链表红黑树”的题,而是考你是否理解哈希分布、容量规划、冲突退化和并发边界。