Java 集合框架进阶:Map 接口深度解析与实战
深入解析 Java Map 接口及其核心实现类。涵盖 HashMap 底层原理(数组 + 链表 + 红黑树)、LinkedHashMap 有序遍历及 LRU 缓存实现、TreeMap 排序特性,以及 ConcurrentHashMap 线程安全方案。通过代码示例对比不同场景下的性能差异与最佳实践,帮助开发者掌握键值对集合的高效应用。

深入解析 Java Map 接口及其核心实现类。涵盖 HashMap 底层原理(数组 + 链表 + 红黑树)、LinkedHashMap 有序遍历及 LRU 缓存实现、TreeMap 排序特性,以及 ConcurrentHashMap 线程安全方案。通过代码示例对比不同场景下的性能差异与最佳实践,帮助开发者掌握键值对集合的高效应用。

💡 掌握 Map 接口的核心特性,理解 Key-Value 键值对的存储结构与设计思想。 💡 熟练掌握 HashMap、LinkedHashMap、TreeMap 等实现类的底层原理与适用场景。 💡 理解 Map 集合的线程安全问题,掌握并发环境下的解决方案。 ⚠️ 本章重点是 HashMap 的底层实现原理 和 不同 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)的场景。
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 的操作。
💡 JDK8 中 HashMap 的底层结构是 数组 + 链表 + 红黑树 的组合结构,目的是解决哈希冲突,提升查询效率。
O(n) 降低到 O(log n)。HashMap 的核心存储流程:
① 📝 计算 Key 的哈希值:通过 hash(key) 方法计算,目的是降低哈希冲突概率。
② 📝 计算数组下标:(数组长度 - 1) & 哈希值,等价于取模运算但效率更高。
③ 📝 判断下标位置是否为空:为空则直接插入新节点;不为空则判断 Key 是否重复。
④ 📝 处理 Key 重复:Key 重复则覆盖 Value;不重复则插入链表或红黑树。
⑤ 📝 扩容判断:当元素个数超过 数组容量 * 加载因子 时,数组会扩容为原来的 2 倍。
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( + size);
hashMap.clear();
System.out.println( + hashMap.isEmpty());
}
}
输出结果
HashMap 内容:{语文=90, 数学=100, 英语=92}
数学成绩:100
包含英语 Key:true
包含 90 分 Value:true
删除语文后的 HashMap:{数学=100, 英语=92}
HashMap 大小:2
清空后是否为空:true
⚠️ 当使用自定义对象作为 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 {
Map<Student, String> studentMap = <>();
(, );
(, );
(, );
studentMap.put(s1, );
studentMap.put(s2, );
studentMap.put(s3, );
studentMap.forEach((key, value) -> System.out.println(key + + value));
}
}
输出结果
Student{id='001', name='张三'}=三班
Student{id='002', name='李四'}=二班
O(1),哈希冲突严重时会退化为 O(n),红黑树转换后为 O(log n)。ConcurrentModificationException 异常。💡 LinkedHashMap 是 HashMap 的子类,底层结构是 HashMap + 双向链表。 它通过双向链表维护键值对的插入顺序或访问顺序,保证遍历顺序与插入顺序一致,或者与最近访问顺序一致。 LinkedHashMap 的元素唯一性判断规则与 HashMap 完全相同。
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
通过构造方法 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 缓存淘汰算法(最近最少使用淘汰)。
💡 TreeMap 的底层结构是红黑树,它会自动对 Key 进行排序,默认是升序排列。
TreeMap 不允许 Key 为 null,因为排序时会抛出空指针异常。
TreeMap 保证 Key 唯一性的方式是通过比较 Key 的大小,而不是 hashCode() 和 equals() 方法。
TreeMap 的两种排序方式:
Comparable 接口,重写 compareTo() 方法。Comparator 比较器,自定义排序规则。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
我们对字符串 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
O(log n),效率低于 HashMap,但支持有序遍历。ClassCastException。💡 Hashtable 是 Map 接口的早期实现类,底层结构是 数组 + 链表(JDK8 没有红黑树优化)。
它的所有方法都添加了 synchronized 关键字,是线程安全的集合。
Hashtable 不允许 Key 或 Value 为 null,默认初始容量为 11,加载因子为 0.75。
💡 ConcurrentHashMap 是 JUC 包下的线程安全集合,专门用于解决 HashMap 的并发问题。
JDK8 中 ConcurrentHashMap 的底层结构是 数组 + 链表 + 红黑树,与 HashMap 类似。
它采用分段锁或 CAS + synchronized 的方式实现线程安全,锁粒度小,并发性能远高于 Hashtable。
ConcurrentHashMap 支持 Key 和 Value 为 null(与 Hashtable 不同)。
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,兼顾线程安全和并发性能。
💡 实现一个 LRU(最近最少使用)缓存工具类,满足以下需求:
利用 LinkedHashMap 的访问顺序模式实现 LRU 缓存,重写 removeEldestEntry() 方法,自定义淘汰规则。
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);
cache.get();
System.out.println( + cache);
cache.put(, );
System.out.println( + cache);
}
}
输出结果
初始缓存:{A=1, B=2, C=3}
访问 A 后的缓存:{B=2, C=3, A=1}
添加 D 后的缓存:{C=3, A=1, D=4}
✅ 这个 LRU 缓存工具类充分利用了 LinkedHashMap 的特性,代码简洁且性能高效。
通过重写 removeEldestEntry() 方法,轻松实现了缓存淘汰规则,在实际开发中可直接用于本地缓存场景。
hashCode() 和 equals() 方法。
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online