Java Map实现类包括HashMap(哈希表高效查询)、LinkedHashMap(维护插入/访问顺序)、TreeMap(红黑树排序)、Hashtable(同步低效)及ConcurrentHashMap(分段锁/CAS高并发),适用于不同场景如缓存、排序和多线程环境。
一、Map 接口核心特性
- 键值对存储:每个元素包含一个键(Key)和一个值(Value)。
- 键唯一性:键不可重复(通过
equals()和hashCode()判断)。 - 无序性(默认):不保证元素的插入顺序(
LinkedHashMap和TreeMap例外)。 - 允许 null:大多数实现类允许键或值为
null(ConcurrentHashMap不允许)。
二、Map 主要实现类对比
| 实现类 | 底层数据结构 | 线程安全 | 顺序特性 | 时间复杂度(插入/查询) | 适用场景 |
|---|---|---|---|---|---|
| HashMap | 数组+链表/红黑树(JDK8+) | 非线程安全 | 无序 | O(1)(平均) | 快速键值查找、无需顺序 |
| LinkedHashMap | 哈希表 + 双向链表 | 非线程安全 | 插入顺序/访问顺序 | O(1) | 需要保留插入顺序或 LRU 缓存 |
| TreeMap | 红黑树 | 非线程安全 | 自然排序/定制排序 | O(log n) | 需要排序或范围查询 |
| Hashtable | 数组+链表 | 线程安全 | 无序 | O(1)(平均) | 遗留代码、简单同步需求 |
| ConcurrentHashMap | 分段锁/CAS(JDK8+) | 线程安全 | 无序 | O(1)(平均) | 高并发读写场景 |
三、各实现类详解
1. HashMap
-
底层数据结构:
- JDK7:数组 + 链表(哈希冲突通过链表解决)。
- JDK8+ :数组 + 链表/红黑树(链表长度 ≥8 时树化,<6 时退化为链表)。
-
哈希计算与索引定位:
// JDK 17 哈希扰动函数 static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } // 索引计算:i = (n - 1) & hash -
扩容机制:
- 初始容量:默认 16,负载因子 0.75。
- 触发条件:元素数量 > 容量 × 负载因子。
- 扩容规则:容量翻倍,旧元素重新哈希到新数组。
- 优化(JDK8+) :通过高位掩码判断元素迁移位置(避免重新计算哈希)。
-
线程安全:非线程安全,需通过
Collections.synchronizedMap()包装或使用ConcurrentHashMap。 -
优点:
- 随机访问速度极快(平均 O(1))。
- 内存占用合理(数组紧凑)。
-
缺点:
- 哈希冲突可能影响性能(链表过长或树化)。
- 不保证顺序。
2. LinkedHashMap
-
底层数据结构:继承
HashMap,通过双向链表维护顺序。// LinkedHashMap.Entry 扩展 Node static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after; // 双向链表指针 } -
顺序模式:
- 插入顺序(默认):按元素添加顺序遍历。
- 访问顺序(
accessOrder=true):最近访问的元素移动到链表尾部(适合实现 LRU 缓存)。
-
LRU 缓存实现示例:
public class LRUCache<K, V> extends LinkedHashMap<K, V> { private final int maxCapacity; public LRUCache(int maxCapacity) { super(maxCapacity, 0.75f, true); this.maxCapacity = maxCapacity; } @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > maxCapacity; } } -
优点:
- 保留插入或访问顺序。
- 适合实现缓存淘汰策略。
-
缺点:
- 内存占用略高于
HashMap(需维护链表指针)。
- 内存占用略高于
3. TreeMap
-
底层数据结构:基于红黑树(自平衡二叉搜索树)。
// 红黑树节点定义 static final class Entry<K,V> implements Map.Entry<K,V> { K key; V value; Entry<K,V> left; Entry<K,V> right; Entry<K,V> parent; boolean color; // 红黑标记 } -
排序规则:
- 自然排序:键实现
Comparable接口。 - 定制排序:通过
Comparator指定排序逻辑。
- 自然排序:键实现
-
范围查询:
// 获取键在 [fromKey, toKey) 范围内的子映射 SortedMap<K, V> subMap = treeMap.subMap(fromKey, toKey); -
优点:
- 支持排序和范围查询。
- 插入、删除、查询稳定在 O(log n)。
-
缺点:
- 内存占用高(红黑树节点结构复杂)。
- 性能低于
HashMap。
4. Hashtable
-
底层数据结构:数组 + 链表(类似 JDK7 的
HashMap)。 -
线程安全:通过
synchronized方法实现全局锁。public synchronized V put(K key, V value) { ... } -
缺点:
- 高并发下性能差(锁竞争激烈)。
- 不允许
null键或值。
-
现状:已被
ConcurrentHashMap取代,不推荐新项目使用。
5. ConcurrentHashMap
-
底层数据结构:
- JDK7:分段锁(Segment 数组 + HashEntry 链表)。
- JDK8+ :数组 + 链表/红黑树,使用
synchronized+ CAS 实现细粒度锁。
-
JDK8+ 核心机制:
- CAS 操作:初始化、节点插入等无锁化。
- 锁粒度:仅锁定哈希桶的头节点(链表或红黑树)。
final V putVal(K key, V value, boolean onlyIfAbsent) { // ... synchronized (f) { // 锁住头节点 if (tabAt(tab, i) == f) { // 处理链表或红黑树插入 } } } -
扩容优化:
- 多线程协助扩容:线程在操作时发现正在扩容,会协助迁移数据。
- 步长分割:每个线程处理固定步长的桶区间。
-
优点:
- 高并发下性能接近非同步的
HashMap。 - 锁粒度细,减少竞争。
- 高并发下性能接近非同步的
-
缺点:
- 实现复杂,内存占用较高。
四、扩容机制对比
| 实现类 | 初始容量 | 负载因子 | 扩容规则 | 触发条件 |
|---|---|---|---|---|
| HashMap | 16 | 0.75 | 容量翻倍 | size > capacity × loadFactor |
| LinkedHashMap | 16 | 0.75 | 同 HashMap | 同 HashMap |
| TreeMap | 无固定容量 | - | 动态扩展红黑树节点 | 无需显式扩容 |
| Hashtable | 11 | 0.75 | 容量翻倍 +1 | 同 HashMap |
| ConcurrentHashMap | 16 | 0.75 | 容量翻倍,分段迁移 | 同 HashMap |
五、线程安全方案对比
| 实现类 | 线程安全实现方式 | 锁粒度 | 性能影响 |
|---|---|---|---|
| Hashtable | 方法级同步(synchronized) | 粗粒度(全局锁) | 高并发下性能差 |
| Collections.synchronizedMap | 对象锁(synchronized) | 粗粒度 | 中等性能开销 |
| ConcurrentHashMap | CAS + synchronized(JDK8+) | 细粒度(桶级别) | 高并发下性能优异 |
六、数据结构图示(Mermaid)
1. Map 类图
«interface»Map+put()+get()+remove()«interface»SortedMap+firstKey()+subMap()«interface»ConcurrentMap+putIfAbsent()HashMapLinkedHashMapTreeMapHashtableConcurrentHashMap
2. HashMap 数据结构(JDK8+)
HashMap
桶1: 链表
数组
桶2: 红黑树
桶N: ...
七、选择建议
-
单线程场景:
- 快速键值查找 →
HashMap。 - 需要插入顺序或 LRU 缓存 →
LinkedHashMap。 - 需要排序或范围查询 →
TreeMap。
- 快速键值查找 →
-
高并发场景:
- 读写均衡 →
ConcurrentHashMap。 - 遗留系统兼容 →
Hashtable(不推荐新项目)。
- 读写均衡 →
-
避免使用:直接使用
Collections.synchronizedMap()包装的HashMap(除非简单同步需求)。
八、示例代码:ConcurrentHashMap 高并发操作
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
ExecutorService executor = Executors.newFixedThreadPool(4);
// 并发添加元素
for (int i = 0; i < 1000; i++) {
executor.submit(() -> {
String key = "key-" + ThreadLocalRandom.current().nextInt(100);
map.compute(key, (k, v) -> (v == null) ? 1 : v + 1);
});
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
System.out.println(map); // 输出各 key 的计数
九、总结
- HashMap:性能最优的无序键值对存储。
- LinkedHashMap:适合需要顺序或缓存淘汰策略的场景。
- TreeMap:唯一支持排序和范围查询的 Map。
- ConcurrentHashMap:高并发场景的首选线程安全 Map。
- Hashtable:遗留类,性能差,不推荐使用。