hashmap实际使用链表法而非开放寻址法:底层为“数组+链表/红黑树”,冲突时挂链表,≥8且数组≥64转红黑树,≤6退化回链表;开放寻址因删除困难、高负载性能差、null键支持不友好、扩容成本高等原因未被采用。

HashMap 实际用的是链表法,不是开放寻址法
Java 标准库中的 HashMap 默认且只使用链表法(也叫链地址法) 解决哈希冲突。它底层是“数组 + 链表/红黑树”结构:当多个键的哈希值经扰动与取模后落在同一数组索引(即桶 bucket)时,新节点就以链表形式挂在这个桶下。Java 8 起采用尾插法,避免多线程扩容时的死循环问题。
链表法的具体行为和优化机制
链表法在实际运行中有一套成熟策略:
- 初始插入都走链表,节点按插入顺序尾部追加
- 当某个桶的链表长度 ≥ 8 且 整个数组长度 ≥ 64 时,该链表自动转为红黑树,查找时间复杂度从 O(n) 降到 O(log n)
- 若红黑树中节点数 ≤ 6,则退化回链表,保持轻量
- 允许一个
null键和任意数量的null值,逻辑清晰自然
开放寻址法在 Java 中基本不用在 HashMap 里
开放寻址法要求所有元素必须存进原数组,靠探测(如线性、平方、双重哈希)找空位。但 HashMap 没有采用它,主要原因包括:
- 删除困难:不能简单清空位置,否则会中断后续探测路径,需引入特殊标记(如 DELETED),增加实现复杂度
- 负载高时性能急剧下降:装载因子超过 0.7 后容易聚集,查找/插入变慢;而链表法对高负载更宽容(即使负载达 10,也只是链变长)
- null 键支持不友好:开放寻址依赖“空位”判断是否存在,但
null键本身也要占一个合法槽位,语义易混淆 - 扩容成本高:所有元素需重新哈希+探测插入;链表法只需重哈希并拆分链表,更高效
开放寻址法在 Java 生态中并非完全缺席
虽然 HashMap 不用,但某些特定场景会用到类似思想:
-
ThreadLocalMap:内部使用线性探测的开放寻址,key 是弱引用,遇到
nullkey 就停止探测并清理 -
高性能第三方库(如 fastutil、trove):提供基于开放寻址的原始类型 Map(如
IntIntHashMap),省去装箱开销、缓存局部性更好 - 教学或嵌入式自定义实现:为控制内存或简化结构,可能手动实现线性/二次探测
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











