JAVA 集合框架进阶:Map 接口的深度解析与实战

1.1 本章学习目标与重点
💡 掌握 Map 接口的核心特性,理解 Key-Value 键值对的存储结构与设计思想。
💡 熟练掌握 HashMap、LinkedHashMap、TreeMap 等实现类的底层原理与适用场景。
💡 理解 Map 集合的线程安全问题,掌握并发环境下的解决方案。
⚠️ 本章重点是 HashMap 的底层实现原理 和 不同 Map 实现类的性能对比,这是面试和开发中的高频核心考点。
1.2 Map 接口核心概述
1.2.1 Map 接口的定义与特性
💡 Map 是一种键值对(Key-Value) 集合,它的核心是通过键(Key)来唯一标识值(Value)。
Map 接口中的 Key 具有唯一性,不能重复;Value 可以重复,并且可以为 null。
Map 接口与 Collection 接口是并列关系,它不属于 Collection 体系,没有继承关系。
Map 接口的核心方法:
put(K key, V value):添加键值对,Key 重复时会覆盖原有 Valueget(Object key):根据 Key 获取 Value,Key 不存在时返回nullremove(Object key):根据 Key 删除对应的键值对containsKey(Object key):判断是否包含指定 KeycontainsValue(Object value):判断是否包含指定 ValuekeySet():获取所有 Key 组成的 Set 集合values():获取所有 Value 组成的 Collection 集合entrySet():获取所有键值对(Map.Entry)组成的 Set 集合
✅ 核心结论:Map 适合通过唯一标识(Key)快速查找对应数据(Value)的场景。
1.2.2 Map 集合的遍历方式
Map 集合有三种常用的遍历方式,我们以 HashMap 为例进行代码实操:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapTraversalDemo {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("name", "张三");
map.put("age", "20");
map.put("gender", "男");
// 方式1:遍历所有Key,通过Key获取Value
System.out.println("方式1:遍历Key获取Value");
Set<String> keySet = map.keySet();
for (String key : keySet) {
String value = map.get(key);
System.out.println(key + "=" + value);
}
// 方式2:遍历所有键值对(Map.Entry)
System.out.println("\n方式2:遍历Map.Entry");
Set<Map.Entry<String, String>> entrySet = map.entrySet();
for (Map.Entry<String, String> entry : entrySet) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
// 方式3:JDK8+ Lambda表达式遍历
System.out.println("\n方式3:Lambda表达式遍历");
map.forEach((key, value) -> System.out.println(key + "=" + value));
}
}
输出结果
方式1:遍历Key获取Value
name=张三
age=20
gender=男
方式2:遍历Map.Entry
name=张三
age=20
gender=男
方式3:Lambda表达式遍历
name=张三
age=20
gender=男
✅ 核心结论:遍历大量数据时,方式2(entrySet)效率最高,因为它避免了通过 Key 重复查询 Value 的操作。
1.3 HashMap:基于哈希表的实现
1.3.1 HashMap 底层原理(JDK8)
💡 JDK8 中 HashMap 的底层结构是 数组 + 链表 + 红黑树 的组合结构,目的是解决哈希冲突,提升查询效率。
- 数组(哈希桶):数组的每个元素是一个链表或红黑树的头节点,默认初始容量为 16,默认加载因子为 0.75。
- 链表:当多个 Key 的哈希值相同,且对应数组下标位置已有元素时,会以链表形式存储,称为哈希冲突。
- 红黑树:当链表长度超过阈值(默认为 8),并且数组长度大于等于 64 时,链表会转换为红黑树,将查询时间复杂度从
O(n)降低到O(log n)。
HashMap 的核心存储流程:
① 📝 计算 Key 的哈希值:通过 hash(key) 方法计算,目的是降低哈希冲突概率。
② 📝 计算数组下标:(数组长度 - 1) & 哈希值,等价于取模运算但效率更高。
③ 📝 判断下标位置是否为空:为空则直接插入新节点;不为空则判断 Key 是否重复。
④ 📝 处理 Key 重复:Key 重复则覆盖 Value;不重复则插入链表或红黑树。
⑤ 📝 扩容判断:当元素个数超过 数组容量 * 加载因子 时,数组会扩容为原来的 2 倍。
1.3.2 代码实操:HashMap 的常用操作
import java.util.HashMap;
import java.util.Map;
public class HashMapDemo {
public static void main(String[] args) {
Map<String, Integer> hashMap = new HashMap<>();
// 1. 添加键值对
hashMap.put("语文", 90);
hashMap.put("数学", 95);
hashMap.put("英语", 92);
hashMap.put("数学", 100); // Key重复,覆盖原有Value
System.out.println("HashMap内容:" + hashMap);
// 2. 根据Key获取Value
Integer mathScore = hashMap.get("数学");
System.out.println("数学成绩:" + mathScore);
// 3. 判断是否包含指定Key或Value
boolean hasEnglish = hashMap.containsKey("英语");
boolean has90 = hashMap.containsValue(90);
System.out.println("包含英语Key:" + hasEnglish);
System.out.println("包含90分Value:" + has90);
// 4. 删除键值对
hashMap.remove("语文");
System.out.println("删除语文后的HashMap:" + hashMap);
// 5. 获取集合大小
int size = hashMap.size();
System.out.println("HashMap大小:" + size);
// 6. 清空集合
hashMap.clear();
System.out.println("清空后是否为空:" + hashMap.isEmpty());
}
}
输出结果
HashMap内容:{语文=90, 数学=100, 英语=92}
数学成绩:100
包含英语Key:true
包含90分Value:true
删除语文后的HashMap:{数学=100, 英语=92}
HashMap大小:2
清空后是否为空:true
1.3.3 自定义对象作为 Key 的注意事项
⚠️ 当使用自定义对象作为 HashMap 的 Key 时,必须重写 hashCode() 和 equals() 方法,否则无法保证 Key 的唯一性。
我们以 Student 类为例,实现基于学号的 Key 唯一性:
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
class Student {
private String id;
private String name;
public Student(String id, String name) {
this.id = id;
this.name = name;
}
// 重写equals方法:根据学号判断Key是否相同
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(id, student.id);
}
// 重写hashCode方法:根据学号计算哈希值
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() {
return "Student{id='" + id + "', name='" + name + "'}";
}
}
public class HashMapCustomKeyDemo {
public static void main(String[] args) {
Map<Student, String> studentMap = new HashMap<>();
Student s1 = new Student("001", "张三");
Student s2 = new Student("002", "李四");
Student s3 = new Student("001", "张三"); // 与s1学号相同
studentMap.put(s1, "一班");
studentMap.put(s2, "二班");
studentMap.put(s3, "三班"); // Key重复,覆盖s1的Value
// 遍历输出
studentMap.forEach((key, value) -> System.out.println(key + "=" + value));
}
}
输出结果
Student{id='001', name='张三'}=三班
Student{id='002', name='李四'}=二班
1.3.4 HashMap 性能分析
- 增删查操作:理想情况下时间复杂度为
O(1),哈希冲突严重时会退化为O(n),红黑树转换后为O(log n)。 - 扩容机制:扩容时需要重新计算所有元素的下标,非常消耗性能,开发中建议提前指定初始容量,减少扩容次数。
⚠️ 注意事项:HashMap 是线程不安全的集合,多线程环境下使用会出现数据错乱或ConcurrentModificationException异常。
1.4 LinkedHashMap:有序的哈希表
1.4.1 LinkedHashMap 底层原理
💡 LinkedHashMap 是 HashMap 的子类,底层结构是 HashMap + 双向链表。
它通过双向链表维护键值对的插入顺序或访问顺序,保证遍历顺序与插入顺序一致,或者与最近访问顺序一致。
LinkedHashMap 的元素唯一性判断规则与 HashMap 完全相同。
1.4.2 代码实操1:插入顺序模式(默认)
import java.util.LinkedHashMap;
import java.util.Map;
public class LinkedHashMapInsertOrderDemo {
public static void main(String[] args) {
Map<String, String> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("b", "B");
linkedHashMap.put("a", "A");
linkedHashMap.put("c", "C");
// 遍历顺序与插入顺序一致
linkedHashMap.forEach((key, value) -> System.out.println(key + "=" + value));
}
}
输出结果
b=B
a=A
c=C
1.4.3 代码实操2:访问顺序模式
通过构造方法 LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) 可以开启访问顺序模式,最近访问的元素会被移到链表尾部。
import java.util.LinkedHashMap;
import java.util.Map;
public class LinkedHashMapAccessOrderDemo {
public static void main(String[] args) {
// 开启访问顺序模式:accessOrder = true
Map<String, String> linkedHashMap = new LinkedHashMap<>(16, 0.75f, true);
linkedHashMap.put("a", "A");
linkedHashMap.put("b", "B");
linkedHashMap.put("c", "C");
System.out.println("初始顺序:");
linkedHashMap.forEach((key, value) -> System.out.println(key + "=" + value));
// 访问元素a,触发访问顺序调整
linkedHashMap.get("a");
System.out.println("\n访问元素a后的顺序:");
linkedHashMap.forEach((key, value) -> System.out.println(key + "=" + value));
}
}
输出结果
初始顺序:
a=A
b=B
c=C
访问元素a后的顺序:
b=B
c=C
a=A
✅ 核心结论:访问顺序模式的 LinkedHashMap 可以用来实现 LRU 缓存淘汰算法(最近最少使用淘汰)。
1.4.4 性能分析
- LinkedHashMap 的增删查效率略低于 HashMap,因为需要维护双向链表的节点引用。
- 适合需要有序遍历且高效查找的场景,例如缓存系统、配置参数存储等。
⚠️ 注意事项:LinkedHashMap 同样是线程不安全的集合。
1.5 TreeMap:基于红黑树的排序映射
1.5.1 TreeMap 底层原理
💡 TreeMap 的底层结构是红黑树,它会自动对 Key 进行排序,默认是升序排列。
TreeMap 不允许 Key 为 null,因为排序时会抛出空指针异常。
TreeMap 保证 Key 唯一性的方式是通过比较 Key 的大小,而不是 hashCode() 和 equals() 方法。
TreeMap 的两种排序方式:
- 自然排序:Key 实现
Comparable接口,重写compareTo()方法。 - 定制排序:创建 TreeMap 时传入
Comparator比较器,自定义排序规则。
1.5.2 代码实操1:自然排序
import java.util.Map;
import java.util.TreeMap;
public class TreeMapNaturalSortDemo {
public static void main(String[] args) {
Map<Integer, String> treeMap = new TreeMap<>();
treeMap.put(3, "C");
treeMap.put(1, "A");
treeMap.put(2, "B");
// 自动按Key升序排列
treeMap.forEach((key, value) -> System.out.println(key + "=" + value));
}
}
输出结果
1=A
2=B
3=C
1.5.3 代码实操2:定制排序
我们对字符串 Key 进行降序排列,通过 Comparator 实现定制排序:
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
public class TreeMapCustomSortDemo {
public static void main(String[] args) {
// 传入比较器,实现Key降序排列
Map<String, Integer> treeMap = new TreeMap<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1); // 降序排列
}
});
treeMap.put("Java", 10);
treeMap.put("Python", 8);
treeMap.put("Go", 9);
treeMap.forEach((key, value) -> System.out.println(key + "=" + value));
}
}
输出结果
Python=8
Java=10
Go=9
1.5.4 性能分析
- 增删查操作:时间复杂度稳定为
O(log n),效率低于 HashMap,但支持有序遍历。 - 适合需要排序和范围查询的场景,例如排行榜、字典排序等。
⚠️ 注意事项:
- TreeMap 是线程不安全的集合。
- 存储自定义对象作为 Key 时,必须指定排序规则,否则会抛出
ClassCastException。
1.6 Hashtable:线程安全的哈希表
1.6.1 Hashtable 核心特性
💡 Hashtable 是 Map 接口的早期实现类,底层结构是 数组 + 链表(JDK8 没有红黑树优化)。
它的所有方法都添加了 synchronized 关键字,是线程安全的集合。
Hashtable 不允许 Key 或 Value 为 null,默认初始容量为 11,加载因子为 0.75。
1.6.2 性能分析
- Hashtable 的线程安全是通过方法加锁实现的,锁粒度大,并发性能低。
- 现代开发中,不推荐使用 Hashtable,优先使用 ConcurrentHashMap 实现线程安全。
1.7 ConcurrentHashMap:并发安全的哈希表
1.7.1 ConcurrentHashMap 核心特性
💡 ConcurrentHashMap 是 JUC 包下的线程安全集合,专门用于解决 HashMap 的并发问题。
JDK8 中 ConcurrentHashMap 的底层结构是 数组 + 链表 + 红黑树,与 HashMap 类似。
它采用分段锁或 CAS + synchronized 的方式实现线程安全,锁粒度小,并发性能远高于 Hashtable。
ConcurrentHashMap 支持 Key 和 Value 为 null(与 Hashtable 不同)。
1.7.2 代码实操:ConcurrentHashMap 的使用
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapDemo {
public static void main(String[] args) {
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
// 多线程环境下安全操作
new Thread(() -> {
for (int i = 0; i < 1000; i++) {
concurrentMap.put("thread1_" + i, i);
}
}).start();
new Thread(() -> {
for (int i = 0; i < 1000; i++) {
concurrentMap.put("thread2_" + i, i);
}
}).start();
// 等待线程执行完成
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ConcurrentHashMap大小:" + concurrentMap.size());
}
}
输出结果
ConcurrentHashMap大小:2000
✅ 核心结论:多线程环境下优先使用 ConcurrentHashMap,兼顾线程安全和并发性能。
1.8 实战案例:基于 Map 实现 LRU 缓存
1.8.1 需求分析
💡 实现一个 LRU(最近最少使用)缓存工具类,满足以下需求:
- 缓存容量有限,超出容量时自动淘汰最近最少使用的元素。
- 支持缓存的添加、查询、删除操作。
- 保证操作的时间复杂度尽可能低。
1.8.2 实现思路
利用 LinkedHashMap 的访问顺序模式实现 LRU 缓存,重写 removeEldestEntry() 方法,自定义淘汰规则。
1.8.3 代码实现
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 基于LinkedHashMap实现的LRU缓存
*/
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
// 缓存最大容量
private final int maxCapacity;
// 构造方法:开启访问顺序模式
public LRUCache(int maxCapacity) {
super(16, 0.75f, true);
this.maxCapacity = maxCapacity;
}
/**
* 重写该方法,自定义淘汰规则
* @param eldest 最久未使用的元素
* @return true表示淘汰该元素,false表示不淘汰
*/
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
// 当元素个数超过最大容量时,淘汰最久未使用的元素
return size() > maxCapacity;
}
// 测试方法
public static void main(String[] args) {
LRUCache<String, Integer> cache = new LRUCache<>(3);
// 添加缓存元素
cache.put("A", 1);
cache.put("B", 2);
cache.put("C", 3);
System.out.println("初始缓存:" + cache);
// 访问元素A,调整访问顺序
cache.get("A");
System.out.println("访问A后的缓存:" + cache);
// 添加元素D,超出容量,淘汰最久未使用的B
cache.put("D", 4);
System.out.println("添加D后的缓存:" + cache);
}
}
输出结果
初始缓存:{A=1, B=2, C=3}
访问A后的缓存:{B=2, C=3, A=1}
添加D后的缓存:{C=3, A=1, D=4}
1.8.4 案例总结
✅ 这个 LRU 缓存工具类充分利用了 LinkedHashMap 的特性,代码简洁且性能高效。
通过重写 removeEldestEntry() 方法,轻松实现了缓存淘汰规则,在实际开发中可直接用于本地缓存场景。
1.9 本章总结
- Map 是键值对集合,Key 唯一,Value 可重复,常用实现类有 HashMap、LinkedHashMap、TreeMap。
- HashMap 底层是数组+链表+红黑树,查询效率高,适合快速查找场景,线程不安全。
- LinkedHashMap 基于 HashMap+双向链表,支持插入顺序或访问顺序遍历,可实现 LRU 缓存。
- TreeMap 底层是红黑树,支持 Key 排序,适合有序遍历和范围查询场景,线程不安全。
- 多线程环境下,优先使用 ConcurrentHashMap 保证线程安全,避免使用 Hashtable。
- 自定义对象作为 HashMap Key 时,必须重写
hashCode()和equals()方法。
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/xcLeigh/article/details/157511906



