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

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

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 重复时会覆盖原有 Value
  • get(Object key):根据 Key 获取 Value,Key 不存在时返回 null
  • remove(Object key):根据 Key 删除对应的键值对
  • containsKey(Object key):判断是否包含指定 Key
  • containsValue(Object value):判断是否包含指定 Value
  • keySet():获取所有 Key 组成的 Set 集合
  • values():获取所有 Value 组成的 Collection 集合
  • entrySet():获取所有键值对(Map.Entry)组成的 Set 集合

✅ 核心结论:Map 适合通过唯一标识(Key)快速查找对应数据(Value)的场景。

1.2.2 Map 集合的遍历方式

Map 集合有三种常用的遍历方式,我们以 HashMap 为例进行代码实操:

importjava.util.HashMap;importjava.util.Map;importjava.util.Set;publicclassMapTraversalDemo{publicstaticvoidmain(String[] args){Map<String,String> map =newHashMap<>(); map.put("name","张三"); map.put("age","20"); map.put("gender","男");// 方式1:遍历所有Key,通过Key获取ValueSystem.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 的底层结构是 数组 + 链表 + 红黑树 的组合结构,目的是解决哈希冲突,提升查询效率。

  1. 数组(哈希桶):数组的每个元素是一个链表或红黑树的头节点,默认初始容量为 16,默认加载因子为 0.75。
  2. 链表:当多个 Key 的哈希值相同,且对应数组下标位置已有元素时,会以链表形式存储,称为哈希冲突
  3. 红黑树:当链表长度超过阈值(默认为 8),并且数组长度大于等于 64 时,链表会转换为红黑树,将查询时间复杂度从 O(n) 降低到 O(log n)

HashMap 的核心存储流程:
① 📝 计算 Key 的哈希值:通过 hash(key) 方法计算,目的是降低哈希冲突概率。
② 📝 计算数组下标:(数组长度 - 1) & 哈希值,等价于取模运算但效率更高。
③ 📝 判断下标位置是否为空:为空则直接插入新节点;不为空则判断 Key 是否重复。
④ 📝 处理 Key 重复:Key 重复则覆盖 Value;不重复则插入链表或红黑树。
⑤ 📝 扩容判断:当元素个数超过 数组容量 * 加载因子 时,数组会扩容为原来的 2 倍。

1.3.2 代码实操:HashMap 的常用操作

importjava.util.HashMap;importjava.util.Map;publicclassHashMapDemo{publicstaticvoidmain(String[] args){Map<String,Integer> hashMap =newHashMap<>();// 1. 添加键值对 hashMap.put("语文",90); hashMap.put("数学",95); hashMap.put("英语",92); hashMap.put("数学",100);// Key重复,覆盖原有ValueSystem.out.println("HashMap内容:"+ hashMap);// 2. 根据Key获取ValueInteger mathScore = hashMap.get("数学");System.out.println("数学成绩:"+ mathScore);// 3. 判断是否包含指定Key或Valueboolean 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 唯一性:

importjava.util.HashMap;importjava.util.Map;importjava.util.Objects;classStudent{privateString id;privateString name;publicStudent(String id,String name){this.id = id;this.name = name;}// 重写equals方法:根据学号判断Key是否相同@Overridepublicbooleanequals(Object o){if(this== o)returntrue;if(o ==null||getClass()!= o.getClass())returnfalse;Student student =(Student) o;returnObjects.equals(id, student.id);}// 重写hashCode方法:根据学号计算哈希值@OverridepublicinthashCode(){returnObjects.hash(id);}@OverridepublicStringtoString(){return"Student{id='"+ id +"',+ name +"'}";}}publicclassHashMapCustomKeyDemo{publicstaticvoidmain(String[] args){Map<Student,String> studentMap =newHashMap<>();Student s1 =newStudent("001","张三");Student s2 =newStudent("002","李四");Student s3 =newStudent("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:插入顺序模式(默认)

importjava.util.LinkedHashMap;importjava.util.Map;publicclassLinkedHashMapInsertOrderDemo{publicstaticvoidmain(String[] args){Map<String,String> linkedHashMap =newLinkedHashMap<>(); 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) 可以开启访问顺序模式,最近访问的元素会被移到链表尾部。

importjava.util.LinkedHashMap;importjava.util.Map;publicclassLinkedHashMapAccessOrderDemo{publicstaticvoidmain(String[] args){// 开启访问顺序模式:accessOrder = trueMap<String,String> linkedHashMap =newLinkedHashMap<>(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 的两种排序方式:

  1. 自然排序:Key 实现 Comparable 接口,重写 compareTo() 方法。
  2. 定制排序:创建 TreeMap 时传入 Comparator 比较器,自定义排序规则。

1.5.2 代码实操1:自然排序

importjava.util.Map;importjava.util.TreeMap;publicclassTreeMapNaturalSortDemo{publicstaticvoidmain(String[] args){Map<Integer,String> treeMap =newTreeMap<>(); 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 实现定制排序:

importjava.util.Comparator;importjava.util.Map;importjava.util.TreeMap;publicclassTreeMapCustomSortDemo{publicstaticvoidmain(String[] args){// 传入比较器,实现Key降序排列Map<String,Integer> treeMap =newTreeMap<>(newComparator<String>(){@Overridepublicintcompare(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,但支持有序遍历。
  • 适合需要排序范围查询的场景,例如排行榜、字典排序等。
    ⚠️ 注意事项:
  1. TreeMap 是线程不安全的集合。
  2. 存储自定义对象作为 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 的使用

importjava.util.Map;importjava.util.concurrent.ConcurrentHashMap;publicclassConcurrentHashMapDemo{publicstaticvoidmain(String[] args){Map<String,Integer> concurrentMap =newConcurrentHashMap<>();// 多线程环境下安全操作newThread(()->{for(int i =0; i <1000; i++){ concurrentMap.put("thread1_"+ i, i);}}).start();newThread(()->{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. 缓存容量有限,超出容量时自动淘汰最近最少使用的元素。
  2. 支持缓存的添加、查询、删除操作。
  3. 保证操作的时间复杂度尽可能低。

1.8.2 实现思路

利用 LinkedHashMap 的访问顺序模式实现 LRU 缓存,重写 removeEldestEntry() 方法,自定义淘汰规则。

1.8.3 代码实现

importjava.util.LinkedHashMap;importjava.util.Map;/** * 基于LinkedHashMap实现的LRU缓存 */publicclassLRUCache<K,V>extendsLinkedHashMap<K,V>{// 缓存最大容量privatefinalint maxCapacity;// 构造方法:开启访问顺序模式publicLRUCache(int maxCapacity){super(16,0.75f,true);this.maxCapacity = maxCapacity;}/** * 重写该方法,自定义淘汰规则 * @param eldest 最久未使用的元素 * @return true表示淘汰该元素,false表示不淘汰 */@OverrideprotectedbooleanremoveEldestEntry(Map.Entry<K,V> eldest){// 当元素个数超过最大容量时,淘汰最久未使用的元素returnsize()> maxCapacity;}// 测试方法publicstaticvoidmain(String[] args){LRUCache<String,Integer> cache =newLRUCache<>(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 本章总结

  1. Map 是键值对集合,Key 唯一,Value 可重复,常用实现类有 HashMap、LinkedHashMap、TreeMap。
  2. HashMap 底层是数组+链表+红黑树,查询效率高,适合快速查找场景,线程不安全。
  3. LinkedHashMap 基于 HashMap+双向链表,支持插入顺序或访问顺序遍历,可实现 LRU 缓存。
  4. TreeMap 底层是红黑树,支持 Key 排序,适合有序遍历和范围查询场景,线程不安全。
  5. 多线程环境下,优先使用 ConcurrentHashMap 保证线程安全,避免使用 Hashtable。
  6. 自定义对象作为 HashMap Key 时,必须重写 hashCode()equals() 方法。

Read more

Spring Boot 3 新特性详解与迁移指南:从 Java 17 到云原生最佳实践

Spring Boot 3 新特性详解与迁移指南:从 Java 17 到云原生最佳实践

Spring Boot 3 新特性详解与迁移指南:从 Java 17 到云原生最佳实践 前言:截至 2026 年 2 月,Spring Boot 3.x 已成为企业级 Java 开发的事实标准。根据最新调研,阿里、字节、腾讯等头部大厂已 100% 完成 Spring Boot 3.2.x 的迁移,3.5.x 作为 3.x 系列的最后一个重大版本,将维护至 2026 年 6 月。然而,从 Spring Boot 2.

By Ne0inhk
计算机毕设 java 奖学金管理信息系统 Java 智能奖学金管理信息系统 基于 SpringBoot 的高校奖学金管理平台

计算机毕设 java 奖学金管理信息系统 Java 智能奖学金管理信息系统 基于 SpringBoot 的高校奖学金管理平台

计算机毕设 java 奖学金管理信息系统 3b2sg9(配套有源码 程序 mysql 数据库 论文)本套源码可以先看具体功能演示视频领取,文末有联 xi 可分享 随着高校学生管理的数字化发展,传统奖学金管理模式存在申请流程繁琐、评审不透明、结果查询不便等问题,难以满足高效化、规范化管理需求。为了优化奖学金管理流程、提升管理效率、保障评审公平公正,开发一款专业化的奖学金管理信息系统成为必然选择,该系统能够为学生和管理员提供高效的申请、评审与查询平台。 该系统采用 Java 语言和 SpringBoot 框架开发,基于 B/S 架构和 MySQL 数据库构建,具备稳定高效的运行性能。核心功能涵盖个人信息管理(注册、登录、资料修改、密码重置)、学生管理(查询、新增、修改、删除学生学号、姓名、院系、专业等)

By Ne0inhk

VSCode Java项目JDK配置全攻略(JDK版本切换不求人)

第一章:VSCode Java项目JDK配置全攻略(JDK版本切换不求人) 在Java开发中,不同项目可能依赖不同版本的JDK,合理配置VSCode中的JDK环境至关重要。通过灵活设置,可实现多版本JDK无缝切换,提升开发效率。 安装与管理多个JDK版本 推荐使用工具如 SDKMAN!(Linux/macOS)或手动下载Oracle/OpenJDK安装包(Windows),集中管理多个JDK版本。安装后,确保各JDK路径清晰,例如: * /usr/lib/jvm/openjdk-11 * /usr/lib/jvm/openjdk-17 * C:\Program Files\Java\jdk-21 配置VSCode中的JDK路径 打开VSCode,在Java项目根目录下创建或修改 .vscode/settings.json文件,指定JDK路径: { // 指定当前项目使用的JDK路径 "java.home": "/usr/lib/

By Ne0inhk