HashMap 底层原理
HashMap 是 Java 中常用的基于哈希表实现的键值对存储结构。其底层实现主要依赖于数组、链表和红黑树(JDK 1.8 引入)。
数据结构
HashMap 内部使用一个 Node<K,V>[]
数组(称为桶数组)存储键值对。每个 Node
包含键、值、哈希值和指向下一个节点的指针(用于链表或红黑树)。
- 默认初始容量为 16,负载因子为 0.75(当元素数量超过容量 × 负载因子时触发扩容)。
- JDK 1.8 后,当链表长度超过 8 且桶数组长度 ≥ 64 时,链表会转换为红黑树以提高查询效率。
哈希计算
HashMap 通过键的 hashCode()
方法计算哈希值,再通过扰动函数(异或和高位运算)减少哈希冲突:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
插入逻辑
- 计算键的哈希值,通过
(n - 1) & hash
确定桶位置(n
为数组长度)。 - 若该位置为空,直接插入新节点。
- 若发生哈希冲突:
- 如果是链表,遍历链表比较键是否已存在(
equals
方法),存在则更新值,否则添加到链表尾部。 - 如果是红黑树,调用树节点的插入方法。
- 如果是链表,遍历链表比较键是否已存在(
- 插入后检查是否需要扩容或树化。
扩容机制
当元素数量超过阈值(容量 × 负载因子)时,HashMap 会扩容为原来的 2 倍:
- 创建新数组,容量为旧数组的 2 倍。
- 重新计算所有节点的位置(
(e.hash & oldCap) == 0
判断是否留在原位置或移动到原索引 + oldCap
的位置)。
线程安全性
HashMap 非线程安全,多线程环境下可能导致数据不一致或死循环(JDK 1.7 链表扩容时)。线程安全替代方案:
ConcurrentHashMap
:分段锁或 CAS 机制。Collections.synchronizedMap
:包装为同步容器。
JDK 1.8 优化
- 链表长度超过 8 且数组长度 ≥ 64 时转为红黑树,查询效率从 O(n) 提升至 O(log n)。
- 扩容时优化了节点重新分配的逻辑,避免重新计算哈希值。
关键点总结
- 哈希冲突通过链表或红黑树解决。
- 扩容触发条件与负载因子紧密相关。
- 红黑树优化了极端情况下的性能问题。