HashMap 的原理是什么?

考查重点:哈希定位、冲突与扩容。哈希定位桶,桶内比键处理冲突,负载增大时扩容;Java HashMap 链表达条件可树化,线程安全需另外保证

本题目录

参考答案

核心回答

以 OpenJDK 25 的 java.util.HashMap 为例,它用“数组定位桶、桶内比较键、随负载扩容”实现键值映射。桶中的冲突节点通常组成链表,符合条件时可转换为红黑树。

  1. 定位桶:取键的 hashCode(),将高位与低位混合得到内部 hash,再用 hash & (容量 - 1) 定位数组下标。数组容量保持为 2 的幂,便于定位和扩容迁移。
  2. 处理冲突:落在同一桶不代表是同一个键,还要比较保存的 hash 及键的引用或 equals()。相同键更新值,不同键继续在链表或树中查找、插入。
  3. 查询:按同样方式定位桶,再找匹配的键。哈希分布良好时,get、put 通常具有平均常数时间的表现;冲突严重时会变慢。
  4. 扩容:新增映射超过容量与负载因子对应的阈值后,通常把数组扩为两倍并重新分配桶内节点,降低拥挤程度。

HashMap 允许一个 null 键和多个 null 值,不保证遍历顺序,也不提供并发修改的线程安全保证。树化改善冲突严重时的查找,不能替代正确的哈希设计和并发控制。

为什么既要 hash,又要 equals?

hash 用来缩小查找范围,equals() 才决定两个不同对象在业务上是否代表同一个键。两个键的 hash 相同,可能只是哈希碰撞;即使内部 hash 不同,也可能因数组只取其中部分位而进入同一个桶。

因此,自定义键需要遵守 equals() 与 hashCode() 的约定:相等的对象必须具有相同的 hashCode,不相等的对象允许碰撞。 只重写 equals() 而没有相应重写 hashCode(),可能使逻辑相等的键落入不同位置,破坏预期的查找和去重行为。

键存入后,也不应再修改参与这两个方法判断的字段。节点保存的是插入时算出的 hash,后来字段变化可能使查询去另一个桶查找;对象还在 Map 中,却不能按原来的预期找到。扩容也不会替应用修复可变键的语义。

链表、红黑树和扩容各解决什么?

链表让多个不同键能够共存在同一桶中,但查找可能要逐个比较。冲突链较长时,实现会尝试树化:表较小时优先扩容,让节点有机会分散到更多桶;表已经足够大,才把相应桶转成红黑树,以控制长链带来的查找开销。树节点结构更复杂,所以不会从一开始就把所有桶都建成树。

扩容关注整个表的负载,树化关注某一个桶的严重冲突,两者不是同一个触发条件。默认负载因子是 0.75;例如数组容量为 16 时,常见阈值是 12。这里的容量指桶数,size 指键值映射数量,不是数组里非空桶的数量;默认构造也通常将实际数组分配留到首次插入时。

容量翻倍后,链表节点利用已保存的 hash 拆成两组,不必重新调用每个键的 hashCode():节点要么保留在原下标,要么移动到“原下标 + 旧容量”。决定因素是 hash 中与旧容量对应的那一位。

例如旧容量为 16,内部 hash 为 3 和 19 的两个键都在下标 3;扩到 32 后,前者仍在 3,后者移到 19。扩容减少了这两个键的桶冲突,但仍要迁移节点并分配新数组,所以已知规模时可以合理预估初始容量,减少反复扩容,也避免过度预分配。

常见追问

变成红黑树后就一定很快吗? 仍依赖键的 hash 和比较性质。树结构有助于处理长链,但大量相同 hash、键又难以区分等情况仍可能增加搜索工作,不能把任何输入下的查询都保证为常数或对数时间。

为什么不能直接让多个线程同时写? HashMap 的节点连接、数组替换、计数等操作没有统一的并发协调,可能出现覆盖、丢失更新或遍历异常。需要并发访问时,可使用合适的并发 Map 或外部同步;涉及“先判断再更新”的复合业务操作,还要明确整体的原子性。

遍历抛出 ConcurrentModificationException 就算并发保护吗? 不是。fail-fast 是尽力发现不合规结构修改的诊断机制,不保证每次都能检测到,也不能用它建立线程安全。

参考资料

  • 国内:JavaGuide:HashMap 源码分析:借鉴桶定位、put/get 与扩容的流程,说明正文中查询写入以及容量、映射数量和负载阈值的区别。
  • 国内:美团技术团队:Java 8 系列之重新认识 HashMap:存储结构与索引计算部分解释数组、链表和红黑树怎样处理冲突,以及容量采用 2 的幂的作用。
  • 国内:沉默王二:Java HashMap 详解:参考 hash 计算与扩容示例,解释正文中桶掩码、高低位混合和容量翻倍后节点分散的机制。
  • 国外:Java SE 25:HashMap:官方性能、负载因子、顺序和同步约定说明 HashMap 提供什么保证,以及 fail-fast 为什么不是并发控制。
  • 国外:Java SE 25:Object:equals() 与 hashCode() 的契约说明如何正确实现自定义键,以及相等与哈希碰撞之间的区别。

相关面经

23 篇

公司未注明

后端开发工程师
查看原面经 →
Java中HashMap的底层数据结构是什么?还有它的扩容机制是什么样的?在哪个JDK版本有改变的?

小红书

国际化开发工程师
校招秋招一面
查看原面经 →
● HashMap的实现机制

水滴公司

AI 全栈工程师
校招秋招一面、二面
查看原面经 →
那比如 Java 的 HashMap 这个数据结构,有了解过吗?

钛松科技

后端开发工程师
查看原面经 →
HashMap 底层原理是什么?

乐堡互娱

AI应用开发岗
校招秋招一面、二面、三面、四面
查看原面经 →
HashMap、重载和重写的区别
展开剩余 18 篇面经

九方智投

Agent 开发工程师
一面
查看原面经 →
hashmap说一下

百度

后端开发工程师
一面
查看原面经 →
HashMap底层实现原理是什么?数据结构是怎么样的?是否线程安全?不使用ConcurrentHashMap、HashTable这种已有实现,你会如何改进现有的HashMap实现线程安全?

蚂蚁集团

面经合集
查看原面经 →
说下 HashMap 的原理,JDK1.8 里 HashMap 做了什么优化?这些优化解决了之前的什么问题?另外它的并发安全问题怎么解?

知乎

全栈开发工程师
秋招一面
查看原面经 →
hashmap理解,是否安全
hashmap内部数据结构

阿里巴巴

AI 应用开发工程师
秋招二面
查看原面经 →
hashmap原理

字节跳动

后端开发工程师
社招一面、二面、三面
查看原面经 →
Java 的hash表是什么? 这是怎么设计的?

阿里巴巴

AI 应用算法工程师
实习
查看原面经 →
HashMap原理

字节跳动、腾讯、蚂蚁集团

面经合集实习
查看原面经 →
哈希表的了解

字节跳动

后端开发工程师(AI / Agent 方向)
实习二面
查看原面经 →
HashMap 底层是什么数据结构?

法大大

Java 开发工程师
实习一面
查看原面经 →
HashMap的数据结构

亚信科技

后端开发工程师(AI / Agent 方向)
查看原面经 →
详细讲一下ArrayList和HashMap

腾讯音乐

后端开发工程师
二面
查看原面经 →
HashMap 底层是怎么实现的?

公司未注明

后端开发工程师
查看原面经 →
2讲一下hashmap底层原理

阿里巴巴

后端开发工程师(AI)
实习一面
查看原面经 →
HashMap的底层实现讲一下?1.7和1.8有什 么区别?

阿里巴巴

AI 应用开发工程师
实习一面
查看原面经 →
java掌握程度:hashmap集合里面有用到集合吗

快手

后端开发工程师
一面
查看原面经 →
HashMap底层数据结构

字节跳动

后端开发工程师(AI)
实习一面
查看原面经 →
介绍hashmap

字节跳动

Agent 开发工程师
查看原面经 →
2 hashmap实现,如何扩容

收录题集

2 个来源,共3 份题集

相关题目

4 题