一、HashMap 核心定义
HashMap 是 Java 集合框架中 Map 接口的哈希表实现 ,位于 java.util 包下,用于存储「键值对(Key-Value)」;允许 Key 和 Value 为 null(仅一个 Key 为 null),线程不安全,元素无序(不保证插入 / 遍历顺序)。
二、底层原理(JDK 8 核心)
HashMap 底层是「数组 + 链表 + 红黑树」的复合结构,核心设计是为了解决哈希冲突,提升查询 / 增删效率:
- 数组(哈希桶,table) :
- 数组类型为
Node<K,V>[](链表节点),每个数组下标对应一个「哈希桶」,桶中存储链表 / 红黑树; - 数组默认初始容量为 16,容量必须是 2 的幂(便于通过
hash & (length-1)替代取模,提升计算效率)。
- 数组类型为
- 链表(解决哈希冲突) :
- 当多个 Key 的哈希值映射到同一个数组下标时,会以链表形式存储(JDK 7 头插法,JDK 8 尾插法,避免扩容时链表成环);
- 链表节点
Node包含 4 个属性:hash(Key 的哈希值)、key、value、next(后继节点)。
- 红黑树(优化链表性能) :
- 当链表长度 ≥ 8 且数组容量 ≥ 64 时,链表转为红黑树(降低查询时间复杂度从 O (n) 到 O (logn));
- 当红黑树节点数 ≤ 6 时,转回链表(红黑树维护成本高,少量节点时链表更高效)。
三、核心机制(面试重中之重)
1. 哈希计算与索引定位
graph LR
A[Key] --> B[计算hashCode()]
B --> C[扰动函数:hash = hashCode() ^ (hashCode() >>> 16)]
C --> D[索引计算:index = hash & (table.length - 1)]
- 扰动函数 :JDK 8 中通过
hash ^ (hash >>> 16)混合哈希码的高位和低位,减少哈希冲突(避免高位特征未被利用); - 索引计算 :用
hash & (length-1)替代hash % length(仅当容量为 2 的幂时等价),位运算效率更高。
2. 扩容机制(resize)
- 触发条件 :
- 元素个数(size)≥ 阈值(threshold = 容量 × 负载因子);
- 链表转红黑树前,发现数组容量 < 64,先扩容而非转树。
- 扩容流程 :
- 新容量 = 原容量 × 2(保持 2 的幂);
- 重新计算每个节点的索引(JDK 8 优化:只需判断哈希值的新增位是 0 还是 1,无需重新计算 hash);
- 将原数组节点迁移到新数组(链表 / 红黑树拆分);
- 默认参数:负载因子(loadFactor)默认 0.75(平衡空间与时间:过小扩容频繁,过大哈希冲突概率高)。
3. 元素插入流程(JDK 8)
graph TD
A[put(Key, Value)] --> B[计算Key的hash值]
B --> C[计算索引index = hash & (length-1)]
C --> D{桶是否为空?}
D -->|是| E[新建Node放入桶中]
D -->|否| F{桶中是红黑树?}
F -->|是| G[红黑树插入节点]
F -->|否| H{Key是否已存在?}
H -->|是| I[替换Value]
H -->|否| J[链表尾部插入节点]
J --> K{链表长度≥8且容量≥64?}
K -->|是| L[链表转红黑树]
K -->|否| M[插入完成]
四、核心特性
表格
| 特性 | 说明 |
|---|---|
| 线程安全 | 线程不安全(无同步锁),多线程并发修改可能导致死循环(JDK 7)/ 数据丢失,需用 ConcurrentHashMap |
| 允许 null 值 | Key 仅允许一个 null(哈希值固定为 0),Value 可多个 null |
| 有序性 | 无序(不保证插入顺序与遍历顺序一致),需有序则用 LinkedHashMap |
| 时间复杂度 | 理想情况(无哈希冲突):增删改查 O (1);最坏情况(全冲突):链表 O (n)、红黑树 O (logn) |
| 初始容量 / 负载因子 | 默认容量 16,负载因子 0.75,可通过构造方法指定(建议初始化时指定容量,避免多次扩容) |
五、常用核心方法
java
运行
// 1. 初始化
HashMap<String, Integer> map = new HashMap<>(); // 默认容量16,负载因子0.75
HashMap<String, Integer> map2 = new HashMap<>(32); // 指定初始容量(会自动调整为2的幂)
HashMap<String, Integer> map3 = new HashMap<>(32, 0.8f); // 指定容量和负载因子
// 2. 增删改查
map.put("java", 1); // 插入键值对(Key存在则替换Value)
map.putIfAbsent("python", 2); // Key不存在时才插入
Integer val = map.get("java"); // 获取Value(Key不存在返回null)
map.remove("python"); // 删除键值对
boolean exists = map.containsKey("java"); // 判断Key是否存在
map.replace("java", 10); // 替换Value(Key存在才生效)
// 3. 遍历(推荐方式)
// 方式1:遍历KeySet
for (String key : map.keySet()) {
System.out.println(key + ":" + map.get(key));
}
// 方式2:遍历EntrySet(效率更高,避免二次get)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
// 方式3:Lambda遍历
map.forEach((k, v) -> System.out.println(k + ":" + v));
// 4. 其他常用
int size = map.size(); // 获取元素个数
map.clear(); // 清空所有元素
Set<Map.Entry<String, Integer>> entrySet = map.entrySet(); // 获取所有键值对
六、面试高频考点
1. JDK 7 vs JDK 8 核心差异
表格
| 维度 | JDK 7 | JDK 8 |
|---|---|---|
| 底层结构 | 数组 + 链表 | 数组 + 链表 + 红黑树 |
| 插入方式 | 头插法(扩容易成环) | 尾插法(避免成环) |
| 哈希计算 | 多次位运算 / 异或(复杂) | 简化为 hash ^ (hash>>> 16) |
| 扩容后索引计算 | 重新计算 hash → 取模 | 按哈希值新增位判断(0/1) |
| 性能 | 链表查询 O (n) | 红黑树查询 O (logn) |
2. 易错点
- Key 的 hashCode 和 equals 重写 :
- 必须同时重写
hashCode()和equals()(否则可能导致 Key 重复存储); - 规则:
equals()相等的 Key,hashCode()必须相等;hashCode()相等的 Key,equals()不一定相等。
- 必须同时重写
- 并发问题 :
- JDK 7 多线程扩容时,链表头插法可能导致死循环;
- JDK 8 解决了死循环,但仍会出现数据丢失 / 覆盖,并发场景优先用
ConcurrentHashMap。
- null Key 处理 :
- null Key 的哈希值固定为 0,索引为 0,因此仅能有一个 null Key。
- 初始容量选择 :
- 若预估存储 1000 个元素,建议初始化容量为
1000 / 0.75 ≈ 1334,再向上取最近的 2 的幂(2048),避免扩容。
- 若预估存储 1000 个元素,建议初始化容量为
3. HashMap vs Hashtable 对比
表格
| 维度 | HashMap | Hashtable |
|---|---|---|
| 线程安全 | 不安全 | 安全(方法加 synchronized) |
| null 支持 | Key/Value 可 null | 不支持 null |
| 性能 | 高(无锁) | 低(全表锁) |
| 底层结构 | 数组 + 链表 + 红黑树(JDK8) | 数组 + 链表 |
| 初始容量 | 16 | 11 |
| 扩容方式 | 2 倍 | 2 倍 + 1 |
七、使用场景
- 适合单线程、频繁增删改查的键值对存储场景;
- 适合对顺序无要求、追求查询效率的场景;
- 不适合并发场景 (用 ConcurrentHashMap)、有序场景(用 LinkedHashMap)。
总结
HashMap 核心关键点:
- JDK 8 底层是数组 + 链表 + 红黑树,哈希计算用扰动函数 + 位运算,扩容保持容量为 2 的幂;
- 红黑树阈值(链表≥8 转树,树≤6 转链表),负载因子默认 0.75,平衡空间与效率;
- 线程不安全,Key 需重写 hashCode/equals,并发场景用 ConcurrentHashMap。