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

苹果最贵手机要来了!折叠屏iPhone将于9月亮相;部分高校严禁校内使用OpenClaw;黄仁勋预言:传统软件和APP或将消失 | 极客头条

苹果最贵手机要来了!折叠屏iPhone将于9月亮相;部分高校严禁校内使用OpenClaw;黄仁勋预言:传统软件和APP或将消失 | 极客头条

「极客头条」—— 技术人员的新闻圈! ZEEKLOG 的读者朋友们好,「极客头条」来啦,快来看今天都有哪些值得我们技术人关注的重要新闻吧。(投稿或寻求报道:[email protected]) 整理 | 郑丽媛 出品 | ZEEKLOG(ID:ZEEKLOGnews) 一分钟速览新闻点! * 多所高校要求警惕 OpenClaw 安全风险,部分严禁校内使用 * 荣耀 CEO 李健:荣耀机器人全栈自研,将聚焦消费市场 * 马化腾凌晨 2 点发声:还有一批龙虾系产品陆续赶来 * 前快手语言大模型中心负责人张富峥,已加入智源人工智能研究院,负责 LLM 方向 * 最新全球 AI 应用百强榜发布,豆包/DeepSeek/千问上榜 * 苹果折叠 iPhone 将于九月亮相,融合 iPhone 与 iPad 体验

By Ne0inhk
不止“996”!曝硅谷AI创业圈「极限工作制」:每天16小时、凌晨3点下班、周末也在写代码

不止“996”!曝硅谷AI创业圈「极限工作制」:每天16小时、凌晨3点下班、周末也在写代码

编译 | 郑丽媛 出品 | ZEEKLOG(ID:ZEEKLOGnews) “如果你周日去旧金山的咖啡馆,会发现几乎每个人都在工作。” 这是 AI 创业公司 Mythril 联合创始人 Sanju Lokuhitige 最近最直观的感受。去年 11 月,他特地搬到旧金山,只为了更接近 AI 创业浪潮的中心。但很快,他也被卷入了这股浪潮带来的另一面——一种越来越极端的工作文化。 Lokuhitige 坦言,他现在几乎每天工作 12 小时,每周 7 天。除了每周少数几场刻意安排的社交活动(主要是为了和创业者们建立联系),其余时间几乎都在写代码、做产品。 “有时候我整整一天都在编程,”他说,“我基本没有什么工作与生活的平衡。”而这样的生活,在如今的 AI 创业圈里并不算罕见。 旧金山 AI 创业圈的真实日常 一位在旧金山一家 AI

By Ne0inhk
黄仁勋公开发文:传统软件开发模式终结,参与AI不必非得拥有计算机博士学位

黄仁勋公开发文:传统软件开发模式终结,参与AI不必非得拥有计算机博士学位

AI 究竟是什么?在 NVIDIA CEO 黄仁勋看来,它早已不只是聊天机器人或某个大模型,而是一种正在迅速成形的“新型基础设施”。 近日,黄仁勋在英伟达官网发布了一篇长文,提出一个颇具形象的比喻——AI 就像一块“五层蛋糕”。从最底层的能源,到芯片、基础设施、模型,再到最上层的应用,人工智能正在形成一整套完整的产业技术栈,并像电力和互联网一样,逐渐成为现代社会的底层能力。 这也是黄仁勋自 2016 年以来公开发表的第七篇长文。在这篇文章中,他从计算机发展史与第一性原理出发,试图解释 AI 技术栈为何会演化成如今的形态,以及为什么全球正在掀起一场规模空前的 AI 基础设施建设。 在他看来,过去几十年的软件大多是预先编写好的程序:人类设计好算法,计算机按指令执行,数据被结构化存储在数据库中,通过精确查询调用。而 AI 的出现打破了这一模式——计算机开始能够理解图像、文本和声音,并根据上下文实时生成答案、推理结果甚至新的内容。 正因为智能不再是预先写好的代码,而是实时生成的能力,支撑它运行的整个计算体系也必须被重新设计。

By Ne0inhk
猛裁1.6万人后,网站再崩6小时、一周4次重大事故!官方“紧急复盘”:跟裁员无关,也不是AI写代码的锅

猛裁1.6万人后,网站再崩6小时、一周4次重大事故!官方“紧急复盘”:跟裁员无关,也不是AI写代码的锅

整理 | 郑丽媛 出品 | ZEEKLOG(ID:ZEEKLOGnews) 过去几年里,科技公司几乎都在同一件事上加速:让 AI 参与写代码。 从自动补全、自动生成函数,到直接修改系统配置,生成式 AI 已经逐渐走进真实生产环境。但最近发生在亚马逊的一连串事故,却给整个行业泼了一盆冷水——当 AI 开始真正参与生产环境开发时,事情可能远比想象复杂。 最近,多家媒体披露,本周二亚马逊内部紧急召开了一场工程“深度复盘(deep dive)”会议,专门讨论最近频繁出现的系统故障——其中,一个被反复提及的关键词是:AI 辅助代码。 一周 4 次严重事故,亚马逊内部紧急复盘 事情的起点,是最近一段时间亚马逊系统稳定性明显下降。 负责亚马逊网站技术架构的高级副总裁 Dave Treadwell 在一封内部邮件中坦言:“各位,正如大家可能已经知道的,最近网站及相关基础设施的可用性确实不太理想。” 为此,公司决定把原本每周例行举行的技术会议

By Ne0inhk