在Java中,Map是一种用于存储**键值对(Key-Value Pair)**的集合,广泛应用于数据缓存、快速查找等场景。
一、Map核心接口
java.util.Map
接口定义了键值对的基本操作:
二、常见Map实现类对比
实现类 | 数据结构 | 线程安全 | 有序性 | 允许Null键/值 | 时间复杂度(平均) |
---|---|---|---|---|---|
HashMap | 数组+链表/红黑树 | 否 | 无序 | 允许 | O(1) |
LinkedHashMap | 链表+哈希表 | 否 | 插入顺序或访问顺序 | 允许 | O(1) |
TreeMap | 红黑树 | 否 | 键的自然排序或自定义 | 不允许Null键 | O(log n) |
ConcurrentHashMap | 分段锁+哈希表 | 是 | 无序 | 不允许Null键/值 | O(1) |
Hashtable | 数组+链表 | 是(全表锁) | 无序 | 不允许 | O(1) |
三、HashMap深度解析
1. 内部结构
-
数组(桶):初始容量(默认16),负载因子(默认0.75)。
-
链表与红黑树:当链表长度≥8时,链表转为红黑树;当红黑树节点≤6时,退化为链表。
2. 哈希冲突解决
-
链地址法:相同哈希值的键值对以链表形式存储。
-
再哈希法:通过
hash()
方法二次计算哈希值(扰动函数减少碰撞)。
3. 扩容机制
-
触发条件:当元素数量 > 容量 × 负载因子时扩容。
-
扩容过程:新建2倍容量数组,重新计算键的哈希并分配到新桶(耗时操作)。
4. 线程安全性
• 非线程安全:多线程并发修改可能导致死循环(JDK1.7链表头插法)或数据丢失。
• 替代方案:使用ConcurrentHashMap或Collections.synchronizedMap()。
四、ConcurrentHashMap(JDK8+)
1. 分段锁优化
-
数据结构:数组 + 链表/红黑树(同HashMap),但使用CAS + synchronized实现线程安全。
-
锁粒度:锁单个桶(链表头节点),减少锁竞争。
2. 关键方法
-
putVal():
3. 性能优势
-
高并发读:无锁访问(volatile变量保证可见性)。
-
写操作:仅锁定当前桶,其他桶可并行操作。
五、TreeMap与红黑树
1. 有序性实现
-
自然排序:键需实现
Comparable
接口。 -
自定义排序:通过
Comparator
指定排序规则。
2. 红黑树特性
-
自平衡二叉查找树:确保查找、插入、删除的时间复杂度为O(log n)。
-
五大规则:
-
节点是红色或黑色。
-
根节点是黑色。
-
叶子节点(NIL)是黑色。
-
红色节点的子节点必须是黑色。
-
任意节点到叶子节点的路径包含相同数量的黑色节点。
-
六、LinkedHashMap与LRU缓存
1. 维护插入顺序
-
双向链表:在哈希表基础上维护条目插入顺序。
-
访问顺序模式:
2. LRU缓存实现
-
最近最少使用:将最近访问的条目移动到链表末尾,淘汰链表头部条目。
七、Map的使用场景与选择建议
场景 | 推荐实现 | 理由 |
---|---|---|
高频查询,无并发 | HashMap | 查询速度快,内存占用小 |
高并发读写 | ConcurrentHashMap | 分段锁保证线程安全,性能高 |
需要排序 | TreeMap | 自然或自定义顺序遍历 |
LRU缓存 | LinkedHashMap | 内置淘汰策略,维护访问顺序 |
遗留系统兼容 | Hashtable | 线程安全但性能低,通常不推荐新项目使用 |
八、Map的性能优化
-
合理初始化容量:减少扩容次数。
-
避免哈希冲突:重写
hashCode()
和equals()
方法,确保键的散列均匀。 -
选择合适实现:根据线程安全和排序需求选择Map类型。
九、常见问题
Q1:HashMap与Hashtable的区别?
-
线程安全:Hashtable全表锁,性能低;HashMap非线程安全。
-
Null支持:Hashtable不允许Null键/值,HashMap允许。
Q2:ConcurrentHashMap如何保证线程安全?
-
JDK7:分段锁(Segment)。
-
JDK8+:CAS + synchronized锁单个桶。
Q3:如何实现线程安全的LRU缓存?
-
使用
LinkedHashMap
并重写removeEldestEntry()
,或结合ConcurrentHashMap
和双向链表。
通过深入理解Map
的实现原理及适用场景,可以在开发中灵活选择最优数据结构,提升系统性能和代码可维护性。