一句话总结:
HashMap 是基于哈希表的键值对集合,通过哈希函数快速定位数据,用链表和红黑树解决冲突,支持动态扩容。
一、核心结构与初始化
-
底层结构:
-
数组(桶) :存储链表的头节点或红黑树的根节点。
-
链表/红黑树:解决哈希冲突,链表长度超过8时转红黑树(退化阈值为6)。
-
默认参数:
- 初始容量:16
- 负载因子:0.75(扩容阈值=容量×负载因子)
- 树化阈值:链表长度≥8
- 链化阈值:红黑树节点≤6
-
-
初始化示例:
// 默认初始化(容量16,负载因子0.75) HashMap<String, Integer> map = new HashMap<>(); // 自定义初始化(容量32,负载因子0.6) HashMap<String, Integer> customMap = new HashMap<>(32, 0.6f);
二、哈希函数与索引计算
-
哈希函数优化:
-
对键的
hashCode()进行扰动,避免低位重复:static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } -
高位参与运算:减少哈希冲突。
-
-
索引计算:
-
利用位运算替代取模(要求数组长度为2的幂):
index = hash(key) & (capacity - 1); // 等价于 hash % capacity
-
三、哈希冲突解决
-
链表法(Java 8 之前):
- 冲突的键值对以链表形式存储在同一个桶中。
- 缺点:链表过长时查找效率低(O(n))。
-
红黑树法(Java 8+):
- 当链表长度≥8,且数组容量≥64时,链表转为红黑树。
- 优点:将查找时间优化至O(log n)。
- 退化条件:红黑树节点数≤6时,退化为链表。
四、动态扩容(Rehashing)
-
触发条件:
- 元素数量 > 容量 × 负载因子(默认12 = 16×0.75)。
-
扩容流程:
- 新容量 = 旧容量 × 2(保持2的幂次)。
- 遍历旧数组,重新计算每个键的索引并迁移到新数组。
- 注意:多线程并发扩容可能导致死循环(链表成环)。
-
性能优化:
- 预分配足够容量,避免频繁扩容。
- 示例:若预计存1000个元素,应初始化容量为2048(1000/0.75≈1333,取最近的2的幂次)。
五、关键操作流程
-
添加元素(
put()) :-
计算键的哈希值及索引。
-
若桶为空,直接插入新节点。
-
若桶为链表/树,遍历查找是否存在相同键:
- 存在则更新值。
- 不存在则追加节点。
-
检查扩容。
-
-
获取元素(
get()) :- 计算索引,遍历链表或树查找键。
-
删除元素(
remove()) :- 定位到桶,移除对应节点。
- 若为红黑树,调整结构。
六、线程安全问题
-
问题:多线程操作可能导致数据覆盖、死循环。
-
解决方案:
- 使用
ConcurrentHashMap(分段锁或CAS机制)。 - 用
Collections.synchronizedMap()包装(性能较低)。
- 使用
七、键对象的约束
-
重写
hashCode()和equals():- 若
equals()返回true,则hashCode()必须相同。 - 反例:未正确重写导致重复键被误存。
- 若
-
示例:
class Key { String id; @Override public int hashCode() { return id.hashCode(); } @Override public boolean equals(Object obj) { return obj instanceof Key && ((Key) obj).id.equals(id); } }
八、性能优化技巧
-
选择合适的初始容量:
// 预计存储2000个元素,初始容量计算: int initialCapacity = (int) (2000 / 0.75) + 1; // 2667 → 4096(2^12) HashMap<String, Integer> map = new HashMap<>(initialCapacity); -
避免频繁修改:批量操作使用
putAll()。 -
键对象不可变:防止哈希值变化导致定位错误。
九、总结
HashMap 设计核心:
- 哈希函数分散键,数组存储桶。
- 链表与红黑树平衡冲突处理效率。
- 动态扩容保持低负载因子。
适用场景:
- 高频查询、低频增删。
- 单线程环境或需高并发时改用
ConcurrentHashMap。
口诀:
「哈希表里数组存,扰动函数算索引
链表红黑解冲突,扩容翻倍性能稳
键重哈希和等值,线程安全要谨慎!」