|
|
@@ -1,4 +1,4 @@
|
|
|
-# 20260731 课堂笔记 — LinkedList 多接口形态 / Iterator 迭代器 / Map集合入门
|
|
|
+# 20260731 课堂笔记 — LinkedList 多接口形态 / Iterator 迭代器 / Map集合(入门·遍历·底层原理)
|
|
|
|
|
|
- **日期**:2026-07-31
|
|
|
- **项目**:`c260731`
|
|
|
@@ -13,8 +13,10 @@
|
|
|
2. [Iterator 迭代器](#2-iterator-迭代器)
|
|
|
3. [Collection 体系回顾:Iterable → Collection](#3-collection-体系回顾iterable--collection)
|
|
|
4. [Map 集合(双列集合)与 HashMap 入门](#4-map-集合双列集合与-hashmap-入门)
|
|
|
-5. [随堂练习要点](#5-随堂练习要点)
|
|
|
-6. [拓展阅读](#6-拓展阅读)
|
|
|
+5. [Map 集合的遍历(keySet + get / entrySet)](#5-map-集合的遍历keyset--get--entryset)
|
|
|
+6. [HashMap 底层原理(哈希表结构)](#6-hashmap-底层原理哈希表结构)
|
|
|
+7. [随堂练习要点](#7-随堂练习要点)
|
|
|
+8. [拓展阅读](#8-拓展阅读)
|
|
|
|
|
|
---
|
|
|
|
|
|
@@ -235,9 +237,21 @@ HashMap<String,Object> map1 = new HashMap<>(); // 方式二:直接用实现
|
|
|
|
|
|
### Map 常用方法
|
|
|
|
|
|
+#### 基本使用
|
|
|
+
|
|
|
| 方法 | 说明 |
|
|
|
|------|------|
|
|
|
| `V put(K key, V value)` | 向 Map 集合中添加元素(键值对) |
|
|
|
+| `boolean containsKey(Object key)` | 判断 Map 集合中是否包含指定的 key 键 |
|
|
|
+| `boolean containsValue(Object value)` | 判断 Map 集合中是否包含指定的 value 值 |
|
|
|
+| `void clear()` | 清空集合,移除所有的键值对元素 |
|
|
|
+| `boolean isEmpty()` | 判断集合是否为空 |
|
|
|
+| `int size()` | 返回集合中的有效元素个数 |
|
|
|
+
|
|
|
+#### 获取元素
|
|
|
+
|
|
|
+| 方法 | 说明 |
|
|
|
+|------|------|
|
|
|
| `V get(Object key)` | 根据 Key 键获取 Value 值 |
|
|
|
| `Set<K> keySet()` | 把当前 Map 集合中的所有 Key 组成一个 Set 集合(无序的单列集合) |
|
|
|
| `Collection<V> values()` | 把所有的 Value 值组合成一个单列集合 |
|
|
|
@@ -289,7 +303,157 @@ public class Demo03 {
|
|
|
|
|
|
---
|
|
|
|
|
|
-## 5. 随堂练习要点
|
|
|
+## 5. Map 集合的遍历(keySet + get / entrySet)
|
|
|
+
|
|
|
+### 概念
|
|
|
+
|
|
|
+Map 存入的元素都是成对出现的(键值对),可以把它看成是「夫妻对」的集合。Map 本身**没有索引**,不能像 List 那样用 `for + get(i)` 直接遍历,需要借助以下两种方式:
|
|
|
+
|
|
|
+**方式一(keySet + get):间接遍历**
|
|
|
+1. 获取所有 Key 集合,使用 `keySet()` 方法实现
|
|
|
+2. 遍历 key 集合,获取到每一个键(可以使用 foreach)
|
|
|
+3. 根据 key 寻找 value,使用 `get(Object key)` 方法来实现
|
|
|
+
|
|
|
+**方式二(entrySet):直接遍历**
|
|
|
+1. 获取所有键值对(Entry)对象的集合,使用 `entrySet()` 方法
|
|
|
+2. 遍历键值对对象的集合
|
|
|
+3. 根据键值对分别获取键和值(`getKey()` / `getValue()`)
|
|
|
+
|
|
|
+### 代码示例
|
|
|
+
|
|
|
+```java
|
|
|
+// 来源:course/Demo04.java
|
|
|
+package course;
|
|
|
+
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
+
|
|
|
+public class Demo04 {
|
|
|
+ public static void main(String[] args) {
|
|
|
+ Map<String,Object> map=new HashMap<>();
|
|
|
+ map.put("x001","张三");
|
|
|
+ map.put("x002","李四");
|
|
|
+ map.put("x003","王五");
|
|
|
+ map.put("x004","赵六");
|
|
|
+
|
|
|
+ System.out.println("---------遍历Map集合:方式一-------------");
|
|
|
+ //1、获取所有Key集合,使用keySet()方法实现。
|
|
|
+ Set<String> set = map.keySet();
|
|
|
+ //2、遍历key集合,获取到每一个键,可以使用foreach
|
|
|
+ for (String key:set){
|
|
|
+ //3、根据key寻找value,使用get(Object key)方法来实现
|
|
|
+ Object value = map.get(key);
|
|
|
+ System.out.println("key:"+key+",value:"+value);
|
|
|
+ }
|
|
|
+ System.out.println("---------遍历Map集合:方式二-------------");
|
|
|
+ //1、获取所有键值对(Entry)对象的集合
|
|
|
+ Set<Map.Entry<String, Object>> entries = map.entrySet();
|
|
|
+ //2、遍历键值对对象的集合
|
|
|
+ for (Map.Entry<String, Object> me:entries){
|
|
|
+ //3、根据键值对分别获取键和值
|
|
|
+ String key = me.getKey();
|
|
|
+ Object value = me.getValue();
|
|
|
+ System.out.println("key:"+key+",value:"+value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 关键理解
|
|
|
+
|
|
|
+- **方式一是「间接遍历」**:先拿 Key 集合,再用 `get(key)` 反查 Value,每次 `get()` 都是一次哈希查找
|
|
|
+- **方式二是「直接遍历」**:一次性拿到键值对 Entry 对象,`getKey()` / `getValue()` 直接取值,效率更高
|
|
|
+- `Map.Entry<K,V>` 是 Map 的**内部类型**,一个 Entry 就代表一个「键值对」
|
|
|
+- 遍历结果依然体现 HashMap 的**无序**特性(顺序 ≠ 添加顺序)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 6. HashMap 底层原理(哈希表结构)
|
|
|
+
|
|
|
+### 概念
|
|
|
+
|
|
|
+`HashMap` 是实现了 Map 接口的一种基于**哈希表结构**实现的集合:
|
|
|
+
|
|
|
+- 哈希表本质来讲,可以理解为 **数组 + 链表**
|
|
|
+- HashMap 依赖 `hashCode` 方法和 `equals` 方法来保证**键 Key 的唯一**
|
|
|
+- 所以**自定义类型**如果想存入 HashMap 的 Key 中,就**一定要重写 `hashCode()` 和 `equals()` 方法**
|
|
|
+
|
|
|
+### 哈希值
|
|
|
+
|
|
|
+哈希值是 JDK 根据**对象的地址、字符串或数字**算出来的 `int` 类型的数值。获取方式:调用 Object 类中的 `hashCode()`,返回的就是哈希码值。
|
|
|
+
|
|
|
+哈希值的特点:
|
|
|
+1. **同一个对象**多次调用 `hashCode()`,返回的哈希值是**相同**的
|
|
|
+2. **默认情况下**,不同的对象哈希值是不同的;重写 `hashCode()` 可以让我们实现不同对象的哈希值相同
|
|
|
+3. 一般是在**多个对象的所有属性值完全相同时**,`hashCode()` 需要返回相同的哈希值
|
|
|
+
|
|
|
+### 哈希表结构演示
|
|
|
+
|
|
|
+```java
|
|
|
+// 来源:course/Demo05.java(注释部分)
|
|
|
+// 上面的数组是默认长度为 16 的数组,添加键值对时先计算键的哈希值(int 整数)
|
|
|
+张三 --hash运算(hashCode) --> 15
|
|
|
+李四 --hash运算(hashCode) --> 87
|
|
|
+王五 --hash运算(hashCode) --> 66
|
|
|
+赵六 --hash运算(hashCode) --> 20
|
|
|
+wanjl --hash运算(hashCode) --> 15 // 与"张三"哈希值相同 → 挂在同一位置的链表上
|
|
|
+哈么么 --hash运算(hashCode) --> 70
|
|
|
+```
|
|
|
+
|
|
|
+### JDK 1.7 及之前:数组 + 链表
|
|
|
+
|
|
|
+存入流程:
|
|
|
+1. 创建**默认长度为 16**、**默认负载因子 0.75** 的数组,数组名 `table`
|
|
|
+2. 根据元素的哈希值跟数组长度进行计算,计算出应该存入的位置
|
|
|
+3. 判断当前位置是否为 `null`,是 `null` 就直接存入
|
|
|
+4. 如果位置不是 `null`(已有元素),就调用 `equals` 方法比较两个元素的属性值,判断是不是同一个
|
|
|
+5. 如果是同一个就不存了;**不是同一个则存入**,老的元素挂在新元素的下边(形成链表)
|
|
|
+
|
|
|
+> 能让两个相同哈希值的对象以链表的方式挂到后面,原因只有一个:**哈希值相同,但 equals 比较不同**。
|
|
|
+
|
|
|
+**风险和弊端**:如果出现越来越多哈希值相同、但 `equals` 比较为 `false` 的元素,就会导致**链表过长,性能下降**。
|
|
|
+
|
|
|
+### JDK 1.8 之后:数组 + 链表 / 红黑树
|
|
|
+
|
|
|
+Java 1.8 之后引入了**红黑树**,1.8 之后的 HashMap 有两种形态:
|
|
|
+
|
|
|
+- **第 1 种**:数组 + 链表(哈希表)
|
|
|
+- **第 2 种**:在哈希表满足了某些条件后,从「数组 + 链表」转换为「数组 + 红黑树」,保证性能不会降低
|
|
|
+
|
|
|
+简单来说:**链表的节点数超过 8 个,就转为红黑树**。
|
|
|
+
|
|
|
+### HashMap 关键常量
|
|
|
+
|
|
|
+| 常量 | 值 | 说明 |
|
|
|
+|------|-----|------|
|
|
|
+| `DEFAULT_INITIAL_CAPACITY` | `16`(`1 << 4`) | 数组默认长度 |
|
|
|
+| `DEFAULT_LOAD_FACTOR` | `0.75f` | 默认负载因子,已存入元素超过数组长度 × 0.75 就对数组扩容 |
|
|
|
+| `TREEIFY_THRESHOLD` | `8` | **树化阈值**:链表节点长度超过 8 就转为红黑树 |
|
|
|
+| `UNTREEIFY_THRESHOLD` | `6` | 当节点长度小于 6 时,从红黑树退化回链表 |
|
|
|
+| `MIN_TREEIFY_CAPACITY` | `64` | 数组 + 链表转红黑树的**第 2 个条件**:数组长度必须大于 64 |
|
|
|
+
|
|
|
+### 代码示例
|
|
|
+
|
|
|
+```java
|
|
|
+// 来源:course/Demo05.java(本类为理论注释讲解,无 main 方法)
|
|
|
+package course;
|
|
|
+
|
|
|
+public class Demo05 {
|
|
|
+ /*
|
|
|
+ HashMap集合
|
|
|
+ HashMap集合是实现了Map接口的一种基于【哈希表结构】实现的集合。
|
|
|
+ HashMap集合依赖 hashCode方法和equals方法来保证 键Key的唯一。
|
|
|
+ 所以如果自定义类型想要存入到HashMap的Key中,就一定要重写【hashCode方法和equals方法】
|
|
|
+ ...
|
|
|
+ (核心内容见上方各小节)
|
|
|
+ */
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 7. 随堂练习要点
|
|
|
|
|
|
### 练习一:LinkedList 多接口引用
|
|
|
|
|
|
@@ -328,9 +492,34 @@ public class Demo03 {
|
|
|
2. 为什么 HashMap 打印时顺序和添加顺序不一致?—— 底层根据键的 hashCode 哈希决定存储位置
|
|
|
3. 为什么 `keySet()` 返回 Set?—— 键不可重复,正好符合 Set 的不可重复特性
|
|
|
|
|
|
+### 练习四:遍历 Map 集合(两种方式)
|
|
|
+
|
|
|
+**核心步骤**:
|
|
|
+1. 创建 `HashMap<String,Object>`,用 `put()` 添加 4 组键值对
|
|
|
+2. **方式一**:`keySet()` 获取所有键 → foreach 遍历键 → `get(key)` 获取值
|
|
|
+3. **方式二**:`entrySet()` 获取所有 Entry → foreach 遍历 → `getKey()` / `getValue()` 获取键值
|
|
|
+4. 对比两种方式的遍历输出
|
|
|
+
|
|
|
+**思考**:
|
|
|
+1. keySet 方式为什么比 entrySet 方式多一次查找?—— keySet 拿到 key 后还要用 `get(key)` 反查 value,entrySet 直接同时拿到键和值
|
|
|
+2. `Map.Entry<String,Object>` 中的泛型怎么理解?—— 键是 String 类型,值是 Object 类型,Entry 就是这个键值对的类型
|
|
|
+3. foreach 遍历 `Set<Map.Entry<...>>` 的底层是什么?—— 编译器翻译成迭代器遍历
|
|
|
+
|
|
|
+### 练习五:HashMap 底层原理理解
|
|
|
+
|
|
|
+**核心步骤**:
|
|
|
+1. 阅读 Demo05 的注释,画出「数组 + 链表」的哈希表结构图
|
|
|
+2. 手写一个自定义类,重写 `hashCode()` 和 `equals()`,验证它能作为 HashMap 的 Key
|
|
|
+3. 说出 JDK1.7 和 JDK1.8 的 HashMap 结构差异
|
|
|
+
|
|
|
+**思考**:
|
|
|
+1. 为什么 HashMap 的 Key 要求重写 `hashCode()` 和 `equals()`?—— 哈希表要计算 Key 的存储位置(hashCode),并判断两个 Key 是否相同(equals),二者缺一不可
|
|
|
+2. 为什么 JDK1.8 要引入红黑树?—— 防止哈希冲突导致链表过长、查询退化为 O(n),红黑树保证 O(log n) 的查询效率
|
|
|
+3. 负载因子 0.75 的含义?—— 当已存元素个数超过容量 × 0.75 时触发扩容(默认 16 → 扩容后约 32)
|
|
|
+
|
|
|
---
|
|
|
|
|
|
-## 6. 拓展阅读
|
|
|
+## 8. 拓展阅读
|
|
|
|
|
|
### 官方文档
|
|
|
|
|
|
@@ -341,11 +530,15 @@ public class Demo03 {
|
|
|
- [Deque (JDK 17)](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Deque.html)
|
|
|
- [Map 接口 (JDK 17)](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Map.html)
|
|
|
- [HashMap (JDK 17)](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/HashMap.html)
|
|
|
+- [Map.Entry (JDK 17)](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Map.Entry.html)
|
|
|
+- [Object.hashCode() (JDK 17)](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html#hashCode())
|
|
|
+- [红黑树 Red-Black Tree(Wikipedia)](https://zh.wikipedia.org/wiki/%E7%BA%A2%E9%BB%91%E6%A0%91)
|
|
|
|
|
|
### 推荐阅读
|
|
|
|
|
|
- 《Java 核心技术 卷 I》第 9 章 集合 —— 迭代器与 foreach 的关系、Map 集合
|
|
|
- 《Effective Java》第 3 版 第 58 条:优先使用 foreach 循环而非传统 for 循环
|
|
|
+- 《Java 编程思想》第 17 章 容器深入研究 —— 散列与哈希码
|
|
|
|
|
|
### 相关知识点
|
|
|
|
|
|
@@ -355,3 +548,4 @@ public class Demo03 {
|
|
|
| LinkedList 双链表理论(0730) | Iterator 迭代器 | 增强 for / forEach 方法 |
|
|
|
| 泛型与集合体系(0729) | Iterable → Collection 继承体系 | Set 集合与 Map 迭代 |
|
|
|
| Collection 单列集合体系 | Map 集合 / HashMap 入门 | Map 遍历(entrySet)、HashMap 底层哈希表 |
|
|
|
+| equals/hashCode 重写(0728) | HashMap 底层原理(数组+链表/红黑树) | HashMap 扩容机制、哈希冲突优化 |
|