分析一下HashMap内部是怎么实现的
当然可以!我们来深入分析一下 Java 中 HashMap
的内部实现机制(以 JDK 8 为主),包括数据结构、核心算法、源码设计、以及适用场景。
🧠 一、HashMap 的核心结构
HashMap
是基于哈希表实现的 Map
,底层结构是数组 + 链表 + 红黑树的结合体:
✅ 数据结构
transient Node<K,V>[] table; // 主体数组,哈希桶(bucket)
- 每个数组元素称为一个“桶”(bucket)。
- 每个桶可能为空,也可能是:
- 链表结构:用于解决哈希冲突;
- 红黑树结构:当链表过长(默认长度 > 8)时转为红黑树,提高查询效率。
🔧 二、HashMap 的核心操作
1️⃣ put(K key, V value) —— 添加元素
主要流程:
-
计算 key 的 hash 值并映射到桶
int hash = hash(key); int index = (n - 1) & hash; // 取模操作(位运算优化)
-
判断是否存在冲突
- 若该桶为空,直接新建节点放入。
- 若已存在节点:
- key 已存在 ⇒ 覆盖旧值;
- key 不存在 ⇒ 添加到链表尾部或插入红黑树。
-
链表长度超过阈值(8):
- 且数组长度 >= 64 时 ⇒ 转为红黑树;
- 否则还是保持链表。
-
是否触发扩容?
- 若当前元素个数 > threshold(一般为 loadFactor × capacity) ⇒ 触发
resize()
。
- 若当前元素个数 > threshold(一般为 loadFactor × capacity) ⇒ 触发
2️⃣ get(Object key) —— 查找元素
- 计算 hash ⇒ 定位到桶;
- 遍历桶内链表或红黑树,比较 key 是否相等;
- 找到即返回 value,未找到返回 null。
3️⃣ resize() —— 扩容机制
- 扩容一般是原容量的 2 倍;
- 将旧数组的所有元素重新 hash 分配到新数组;
- 扩容是最消耗性能的操作之一,会导致 O(n) 的开销;
- 所以建议创建
HashMap
时合理指定初始容量。
int newCap = oldCap << 1;
📦 三、Node 节点结构(源码简化)
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next; // 链表结构
}
若该桶变为红黑树,则会变成 TreeNode(继承 Node):
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent;
TreeNode<K,V> left;
TreeNode<K,V> right;
boolean red;
}
🧠 四、哈希函数和哈希扰动
为了避免哈希冲突,JDK 8 对 hashCode()
结果做了“扰动处理”:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这种处理方式能让高位参与低位运算,提高 hash 值的随机性,从而减少哈希冲突。
🚩 五、红黑树的使用条件
条件 | 是否转为红黑树 |
---|---|
链表长度 > 8,且数组长度 >= 64 | ✅ 转为红黑树 |
链表长度 > 8,数组长度 < 64 | ❌ 不转,优先扩容 |
树节点个数 < 6 | ✅ 转回链表(treeify → untreeify) |
这样做的好处是避免在小容量下使用红黑树带来的额外复杂度和性能负担。
📈 六、时间复杂度分析
操作 | 最佳情况 | 最坏情况(大量哈希冲突) |
---|---|---|
put/get/remove | O(1) | O(log n)(红黑树)或 O(n)(链表) |
resize | O(n) | O(n) |
📌 七、HashMap 线程安全性
- 非线程安全!
- 多线程下容易出现 数据覆盖、死循环(JDK 7)、丢失更新等问题。
- 如果多线程环境:
- 可使用:
Collections.synchronizedMap(new HashMap())
- 或直接用:
ConcurrentHashMap
(更高效,分段锁/红黑树优化)
- 可使用:
✅ 八、应用场景
适合以下场景:
- 查找、插入、删除频繁且对性能要求高;
- Key 无序、无需排序;
- Key 避免过多 hash 冲突(选择好的
hashCode()
方法); - 单线程或开发者自行管理并发问题。
🧪 九、底层源码建议阅读入口(JDK 8)
从 HashMap.java
的以下方法入手:
putVal(...)
:添加元素的核心逻辑resize()
:扩容处理treeifyBin(...)
:链表转红黑树getNode(...)
:查找逻辑