编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

  • Cpp入门到精通

  • Java入门精通

    • README
    • 入门教程

      • README
      • 基础语法
      • 数据类型
      • 运算符
      • 字符串和数组
      • 流程语句
      • 函数方法
      • 类和对象
      • 继承和多态
      • 接口和抽象类
      • 异常处理
      • 集合框架
        • 11.1 集合概述
          • 11.1.1 为什么需要集合
          • 11.1.2 集合体系结构
          • 11.1.3 综合案例:集合类型选择指南
          • 11.1.4 训练题
        • 11.2 List集合
          • 11.2.1 ArrayList
          • 11.2.2 LinkedList
          • 11.2.3 ArrayList和LinkedList区别
          • 11.2.4 综合案例:待办事项列表
          • 11.2.5 List训练题
        • 11.3 Set集合
          • 11.3.1 HashSet
          • 11.3.2 TreeSet
          • 11.3.3 综合案例:标签管理系统
          • 11.3.4 Set训练题
        • 11.4 Map集合
          • 11.4.1 HashMap
          • 11.4.2 TreeMap
          • 11.4.3 Map遍历方式
          • 11.4.4 综合案例:单词频率统计器
          • 11.4.5 训练题
        • 11.5 集合遍历
          • 11.5.1 Iterator迭代器
          • 11.5.2 for-each遍历
          • 11.5.3 Stream流(JDK8+)
          • 11.5.4 综合案例:学生成绩分析器
          • 11.5.5 训练题
        • 11.6 Collections工具类
          • 11.6.1 综合案例:集合工具箱
          • 11.6.2 训练题
      • IO流和File
      • 线程和锁
      • 泛型
      • 注解和反射
    • 综合案例

    • 专栏博客

  • Go入门到精通

  • JavaScript入门

  • CodeX
  • Java入门精通
  • 入门教程
杨充
2026-04-07
目录

集合框架

# 11.集合框架

# 目录介绍

  • 11.1 集合概述
    • 11.1.1 为什么需要集合
    • 11.1.2 集合体系结构
    • 11.1.3 综合案例:集合类型选择指南
    • 11.1.4 训练题
  • 11.2 List集合
    • 11.2.1 ArrayList
    • 11.2.2 LinkedList
    • 11.2.3 ArrayList和LinkedList区别
    • 11.2.4 综合案例:待办事项列表
    • 11.2.5 List训练题
  • 11.3 Set集合
    • 11.3.1 HashSet
    • 11.3.2 TreeSet
    • 11.3.3 综合案例:标签管理系统
    • 11.3.4 Set训练题
  • 11.4 Map集合
    • 11.4.1 HashMap
    • 11.4.2 TreeMap
    • 11.4.3 Map遍历方式
    • 11.4.4 综合案例:单词频率统计器
    • 11.4.5 训练题
  • 11.5 集合遍历
    • 11.5.1 Iterator迭代器
    • 11.5.2 for-each遍历
    • 11.5.3 Stream流(JDK8+)
    • 11.5.4 综合案例:学生成绩分析器
    • 11.5.5 训练题
  • 11.6 Collections工具类
    • 11.6.1 综合案例:集合工具箱
    • 11.6.2 训练题

# 11.1 集合概述

# 11.1.1 为什么需要集合

数组的局限:大小固定,不能动态扩容;只能存储同一类型元素;增删操作不方便。

集合的优势:大小动态变化;提供丰富的操作方法(增删改查排序等);可以存储不同类型的对象(配合泛型更安全)。

对比 C++:C++ 的 STL 提供了 vector、list、set、map 等容器,Java 的集合框架与之对应。

# 11.1.2 集合体系结构

Collection(接口)
├── List(有序、可重复)
│   ├── ArrayList(数组实现,查询快)
│   ├── LinkedList(链表实现,增删快)
│   └── Vector(线程安全,已过时)
├── Set(无序、不重复)
│   ├── HashSet(哈希表实现)
│   ├── LinkedHashSet(有序的HashSet)
│   └── TreeSet(红黑树,有序)
└── Queue(队列)
    ├── LinkedList
    └── PriorityQueue

Map(接口,键值对)
├── HashMap(哈希表实现)
├── LinkedHashMap(有序的HashMap)
├── TreeMap(红黑树,有序)
└── Hashtable(线程安全,已过时)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 11.1.3 综合案例:集合类型选择指南

本案例通过一个实际场景演示不同集合类型的特点和选择依据。

import java.util.*;

/**
 * 集合类型选择指南 —— 集合概述综合案例
 * 知识点:集合体系、List/Set/Map/Queue 的特点和选择
 */
public class CollectionSelector {
    public static void main(String[] args) {
        System.out.println("===== 集合类型选择指南 =====\n");

        // 1. List:有序、可重复
        System.out.println("--- List(有序可重复)---");
        List<String> list = new ArrayList<>(List.of("Java", "Python", "Java", "Go"));
        System.out.println("  内容: " + list);
        System.out.println("  大小: " + list.size());         // 4(含重复)
        System.out.println("  索引访问: list.get(0) = " + list.get(0));

        // 2. Set:无序、不重复
        System.out.println("\n--- Set(无序不重复)---");
        Set<String> set = new HashSet<>(list);  // 自动去重
        System.out.println("  内容: " + set);
        System.out.println("  大小: " + set.size());          // 3(去重)
        System.out.println("  包含Java? " + set.contains("Java"));

        // 3. Map:键值对
        System.out.println("\n--- Map(键值对)---");
        Map<String, Integer> map = new HashMap<>();
        for (String lang : list) {
            map.merge(lang, 1, Integer::sum);  // 统计出现次数
        }
        System.out.println("  统计: " + map);  // {Java=2, Python=1, Go=1}

        // 4. Queue:先进先出
        System.out.println("\n--- Queue(先进先出)---");
        Queue<String> queue = new LinkedList<>();
        queue.offer("任务A");
        queue.offer("任务B");
        queue.offer("任务C");
        System.out.println("  队首: " + queue.peek());    // 任务A
        System.out.println("  出队: " + queue.poll());    // 任务A
        System.out.println("  剩余: " + queue);

        // 5. 选择建议总结
        System.out.println("\n--- 选择建议 ---");
        System.out.println("  需要索引访问+有序     → ArrayList");
        System.out.println("  需要频繁首尾增删      → LinkedList");
        System.out.println("  需要去重              → HashSet");
        System.out.println("  需要去重+排序         → TreeSet");
        System.out.println("  需要键值映射          → HashMap");
        System.out.println("  需要键值映射+键排序   → TreeMap");
        System.out.println("  需要先进先出          → LinkedList/ArrayDeque");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# 11.1.4 训练题

  1. 选择题:以下哪个集合接口允许元素重复?

    • A. Set B. List C. Map D. Queue
  2. 填空题:Collection 接口下有三大子接口:___、___ 和 ___。Map 不继承 Collection。

  3. 思考题:Java 集合框架为什么要分为 Collection 和 Map 两大体系?如果把 Map 也设计为 Collection 的子接口,会有什么问题?

# 11.2 List集合

# 11.2.1 ArrayList

ArrayList 的底层原理:ArrayList 内部使用 Object[] elementData 数组存储元素。默认初始容量为 10(首次 add 时才分配),当容量不足时按 oldCapacity + (oldCapacity >> 1) 扩容(即 1.5 倍)。扩容时调用 Arrays.copyOf() 创建新数组并复制数据,因此频繁扩容会影响性能。如果预知元素数量,建议使用 new ArrayList<>(1000) 预分配容量。get(index) 直接通过数组下标访问(O(1)),而 add(index, element) 和 remove(index) 需要调用 System.arraycopy() 移动后续元素(O(n))。

import java.util.ArrayList;

ArrayList<String> list = new ArrayList<>();

// 添加
list.add("张三");
list.add("李四");
list.add("王五");
list.add(1, "赵六");  // 在索引1处插入

// 获取
String name = list.get(0);  // 张三
int size = list.size();      // 4

// 修改
list.set(0, "张三丰");

// 删除
list.remove(0);           // 按索引删除
list.remove("李四");       // 按元素删除

// 查找
boolean contains = list.contains("王五");  // true
int index = list.indexOf("王五");           // 返回索引

// 遍历
for (String n : list) {
    System.out.println(n);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 11.2.2 LinkedList

import java.util.LinkedList;

LinkedList<String> list = new LinkedList<>();
list.add("张三");
list.add("李四");

// LinkedList 特有方法
list.addFirst("头部");
list.addLast("尾部");
String first = list.getFirst();
String last = list.getLast();
list.removeFirst();
list.removeLast();
1
2
3
4
5
6
7
8
9
10
11
12
13

# 11.2.3 ArrayList和LinkedList区别

对比项 ArrayList LinkedList
底层结构 动态数组 双向链表
随机访问 O(1),快 O(n),慢
增删(头部/中间) O(n),慢 O(1),快
内存占用 较少 较多(每个节点存储前后指针)
适用场景 查询多,增删少 增删多,查询少

对比 C++:ArrayList 对应 std::vector,LinkedList 对应 std::list。

疑惑:LinkedList 增删真的比 ArrayList 快吗?

答疑:这个结论是有条件的。LinkedList 在已知节点位置时增删确实是 O(1),但查找节点本身需要 O(n)。在实际测试中,由于 CPU 缓存友好性(数组是连续内存,链表节点分散),ArrayList 在大多数场景下性能都优于 LinkedList。

论证:

// 实测:ArrayList vs LinkedList 在中间位置插入 10 万次
ArrayList<Integer> arrayList = new ArrayList<>();
LinkedList<Integer> linkedList = new LinkedList<>();

// ArrayList 中间插入:每次都要 arraycopy 移动元素
// 但数组是连续内存,CPU 缓存命中率高
for (int i = 0; i < 100000; i++) {
    arrayList.add(arrayList.size() / 2, i);  // 约 1200ms
}

// LinkedList 中间插入:先要从头遍历到中间位置(O(n)),然后插入(O(1))
for (int i = 0; i < 100000; i++) {
    linkedList.add(linkedList.size() / 2, i);  // 约 20000ms!
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

结果展示:由于 LinkedList 需要先遍历定位,实际上中间位置插入远慢于 ArrayList。在绝大多数实际场景中,ArrayList 是更好的选择。JDK 官方文档也建议首选 ArrayList。

# 11.2.4 综合案例:待办事项列表

本案例综合运用 ArrayList 和 LinkedList 的特性,实现一个支持增删改查和撤销的待办列表。

import java.util.*;

/**
 * 待办事项列表 —— List集合综合案例
 * 知识点:ArrayList增删改查、LinkedList首尾操作、List排序、性能对比
 */
public class TodoList {
    private ArrayList<String> todos = new ArrayList<>();
    private LinkedList<String> undoStack = new LinkedList<>(); // 用作栈

    public void add(String task) {
        todos.add(task);
        undoStack.push("ADD:" + task);  // LinkedList.push = addFirst
        System.out.println("  添加: " + task);
    }

    public void insert(int index, String task) {
        if (index >= 0 && index <= todos.size()) {
            todos.add(index, task);  // ArrayList 中间插入
            undoStack.push("INSERT:" + index + ":" + task);
            System.out.println("  插入到位置" + index + ": " + task);
        }
    }

    public void complete(int index) {
        if (index >= 0 && index < todos.size()) {
            String removed = todos.remove(index);  // ArrayList 按索引删除
            undoStack.push("COMPLETE:" + index + ":" + removed);
            System.out.println("  完成: " + removed);
        }
    }

    public void undo() {
        if (undoStack.isEmpty()) {
            System.out.println("  无操作可撤销");
            return;
        }
        String action = undoStack.pop();  // LinkedList.pop = removeFirst
        String[] parts = action.split(":", 3);
        switch (parts[0]) {
            case "ADD" -> {
                todos.remove(todos.size() - 1);
                System.out.println("  撤销添加: " + parts[1]);
            }
            case "INSERT" -> {
                todos.remove(Integer.parseInt(parts[1]));
                System.out.println("  撤销插入: " + parts[2]);
            }
            case "COMPLETE" -> {
                todos.add(Integer.parseInt(parts[1]), parts[2]);
                System.out.println("  撤销完成: " + parts[2]);
            }
        }
    }

    public void show() {
        if (todos.isEmpty()) {
            System.out.println("  (空)");
            return;
        }
        for (int i = 0; i < todos.size(); i++) {
            System.out.printf("  %d. %s%n", i + 1, todos.get(i));  // ArrayList O(1) 索引访问
        }
    }

    public static void main(String[] args) {
        System.out.println("===== 待办事项列表 =====\n");
        TodoList todo = new TodoList();

        todo.add("学习 ArrayList");
        todo.add("学习 LinkedList");
        todo.add("完成训练题");
        todo.insert(1, "复习数组知识");

        System.out.println("\n当前列表:");
        todo.show();

        System.out.println("\n完成任务:");
        todo.complete(0);

        System.out.println("\n当前列表:");
        todo.show();

        System.out.println("\n撤销操作:");
        todo.undo();

        System.out.println("\n当前列表:");
        todo.show();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90

运行结果:

===== 待办事项列表 =====

  添加: 学习 ArrayList
  添加: 学习 LinkedList
  添加: 完成训练题
  插入到位置1: 复习数组知识

当前列表:
  1. 学习 ArrayList
  2. 复习数组知识
  3. 学习 LinkedList
  4. 完成训练题

完成任务:
  完成: 学习 ArrayList

当前列表:
  1. 复习数组知识
  2. 学习 LinkedList
  3. 完成训练题

撤销操作:
  撤销完成: 学习 ArrayList

当前列表:
  1. 学习 ArrayList
  2. 复习数组知识
  3. 学习 LinkedList
  4. 完成训练题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 11.2.5 List训练题

训练1:实现一个方法 List<Integer> removeDuplicates(List<Integer> list),去除列表中的重复元素并保持原始顺序。分别用 LinkedHashSet 和 Stream API 两种方式实现。

训练2:使用 ArrayList 实现一个简单的"撤销"功能:维护一个操作历史列表,支持 execute(String command) 和 undo() 操作。

# 11.3 Set集合

# 11.3.1 HashSet

import java.util.HashSet;

HashSet<String> set = new HashSet<>();
set.add("张三");
set.add("李四");
set.add("张三");  // 重复元素不会添加

System.out.println(set.size());      // 2
System.out.println(set.contains("张三")); // true

set.remove("张三");

for (String name : set) {
    System.out.println(name);  // 顺序不确定
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 11.3.2 TreeSet

import java.util.TreeSet;

TreeSet<Integer> set = new TreeSet<>();
set.add(30);
set.add(10);
set.add(20);

System.out.println(set);  // [10, 20, 30](自动排序)
System.out.println(set.first());  // 10
System.out.println(set.last());   // 30
1
2
3
4
5
6
7
8
9
10

# 11.3.3 综合案例:标签管理系统

本案例运用 HashSet 去重和 TreeSet 排序特性,实现文章标签管理。

import java.util.*;

/**
 * 标签管理系统 —— Set集合综合案例
 * 知识点:HashSet去重、TreeSet排序、集合运算(交/并/差)
 */
public class TagManager {
    // 每篇文章的标签用 HashSet(去重)
    private Map<String, HashSet<String>> articleTags = new HashMap<>();

    public void addTags(String article, String... tags) {
        HashSet<String> tagSet = articleTags.computeIfAbsent(article, k -> new HashSet<>());
        Collections.addAll(tagSet, tags);
    }

    // 集合运算:两篇文章的共同标签(交集)
    public Set<String> commonTags(String article1, String article2) {
        Set<String> s1 = new HashSet<>(articleTags.getOrDefault(article1, new HashSet<>()));
        s1.retainAll(articleTags.getOrDefault(article2, new HashSet<>()));
        return s1;
    }

    // 所有标签(并集),用 TreeSet 自动排序
    public TreeSet<String> allTags() {
        TreeSet<String> all = new TreeSet<>();
        for (HashSet<String> tags : articleTags.values()) {
            all.addAll(tags);
        }
        return all;
    }

    // 文章独有标签(差集)
    public Set<String> uniqueTags(String article, String other) {
        Set<String> s1 = new HashSet<>(articleTags.getOrDefault(article, new HashSet<>()));
        s1.removeAll(articleTags.getOrDefault(other, new HashSet<>()));
        return s1;
    }

    public static void main(String[] args) {
        System.out.println("===== 标签管理系统 =====\n");
        TagManager mgr = new TagManager();

        mgr.addTags("Java入门", "Java", "编程", "入门", "面向对象");
        mgr.addTags("Python入门", "Python", "编程", "入门", "脚本");
        mgr.addTags("Java入门", "Java", "JVM");  // 重复的 "Java" 自动去重

        System.out.println("--- 各文章标签 ---");
        mgr.articleTags.forEach((article, tags) ->
                System.out.println("  " + article + ": " + tags));

        System.out.println("\n--- 交集(共同标签)---");
        System.out.println("  " + mgr.commonTags("Java入门", "Python入门"));

        System.out.println("\n--- 并集(所有标签,TreeSet排序)---");
        System.out.println("  " + mgr.allTags());

        System.out.println("\n--- 差集(Java入门独有)---");
        System.out.println("  " + mgr.uniqueTags("Java入门", "Python入门"));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

运行结果:

===== 标签管理系统 =====

--- 各文章标签 ---
  Java入门: [Java, JVM, 入门, 编程, 面向对象]
  Python入门: [Python, 入门, 编程, 脚本]

--- 交集(共同标签)---
  [入门, 编程]

--- 并集(所有标签,TreeSet排序)---
  [JVM, Java, Python, 入门, 编程, 脚本, 面向对象]

--- 差集(Java入门独有)---
  [Java, JVM, 面向对象]
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 11.3.4 Set训练题

训练1:给定两个集合 Set<String> a 和 Set<String> b,编写方法计算它们的交集、并集和差集。

Set<String> a = Set.of("Java", "Python", "Go");
Set<String> b = Set.of("Python", "Rust", "Go");
// 交集:[Python, Go]  并集:[Java, Python, Go, Rust]  差集:[Java]
1
2
3

训练2:HashSet 判断元素重复依赖 hashCode() 和 equals()。如果一个对象只重写了 equals() 而没有重写 hashCode(),放入 HashSet 会出现什么问题?编写代码验证。

思考:HashSet 底层使用 HashMap 实现——每个元素作为 HashMap 的 key,value 统一是一个 PRESENT 常量对象。这种实现方式有什么优势?

# 11.4 Map集合

# 11.4.1 HashMap

HashMap 的底层原理:JDK 8 的 HashMap 采用数组 + 链表 + 红黑树结构。内部是一个 Node<K,V>[] 数组(称为桶 bucket),默认初始容量 16,负载因子 0.75。put(key, value) 时先计算 key.hashCode() 并经过扰动函数 (h = key.hashCode()) ^ (h >>> 16) 得到桶索引;若该桶已有元素(哈希冲突),则以链表方式追加;当同一桶的链表长度 ≥ 8 且数组长度 ≥ 64 时,链表会转化为红黑树(查找从 O(n) 优化到 O(log n))。当元素总数超过 容量 × 负载因子 时进行 2 倍扩容,所有元素重新散列。这就是为什么自定义对象作为 key 时必须同时重写 hashCode() 和 equals()。

import java.util.HashMap;

HashMap<String, Double> accounts = new HashMap<>();

// 添加键值对
accounts.put("张三", 1000.0);
accounts.put("李四", 2000.0);
accounts.put("王五", 3000.0);

// 获取
double balance = accounts.get("张三");  // 1000.0
double defaultVal = accounts.getOrDefault("赵六", 0.0);  // 0.0

// 判断
boolean hasKey = accounts.containsKey("张三");    // true
boolean hasVal = accounts.containsValue(1000.0);  // true

// 删除
accounts.remove("王五");

// 大小
int size = accounts.size();  // 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 11.4.2 TreeMap

import java.util.TreeMap;

TreeMap<String, Double> map = new TreeMap<>();
map.put("Charlie", 3000.0);
map.put("Alice", 1000.0);
map.put("Bob", 2000.0);

System.out.println(map);  // {Alice=1000.0, Bob=2000.0, Charlie=3000.0}(按key排序)
1
2
3
4
5
6
7
8

# 11.4.3 Map遍历方式

HashMap<String, Double> map = new HashMap<>();
map.put("张三", 1000.0);
map.put("李四", 2000.0);

// 方式1:遍历 entrySet
for (Map.Entry<String, Double> entry : map.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

// 方式2:遍历 keySet
for (String key : map.keySet()) {
    System.out.println(key + ": " + map.get(key));
}

// 方式3:forEach (JDK 8+)
map.forEach((key, value) -> {
    System.out.println(key + ": " + value);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

对比 C++:HashMap 对应 std::unordered_map,TreeMap 对应 std::map。

# 11.4.4 综合案例:单词频率统计器

本案例综合运用 HashMap 和 TreeMap,实现文本单词频率统计和排序。

import java.util.*;
import java.util.stream.Collectors;

/**
 * 单词频率统计器 —— Map集合综合案例
 * 知识点:HashMap存储、merge()方法、TreeMap排序、Map遍历方式
 */
public class WordCounter {
    public static void main(String[] args) {
        String text = "Java is great Java is popular Python is also great";

        System.out.println("===== 单词频率统计器 =====");
        System.out.println("文本: " + text + "\n");

        // 1. HashMap 统计(merge 方法)
        HashMap<String, Integer> freqMap = new HashMap<>();
        for (String word : text.split("\\s+")) {
            freqMap.merge(word, 1, Integer::sum);  // key存在则累加,不存在则put
        }
        System.out.println("--- HashMap 统计结果(无序)---");
        freqMap.forEach((word, count) ->
                System.out.printf("  %-10s : %d%n", word, count));

        // 2. TreeMap 按字母排序
        TreeMap<String, Integer> sortedMap = new TreeMap<>(freqMap);
        System.out.println("\n--- TreeMap 按字母排序 ---");
        sortedMap.forEach((word, count) ->
                System.out.printf("  %-10s : %d%n", word, count));

        // 3. 按频率降序排序(entrySet + Stream)
        System.out.println("\n--- 按频率降序 ---");
        freqMap.entrySet().stream()
                .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
                .forEach(e -> System.out.printf("  %-10s : %d%n", e.getKey(), e.getValue()));

        // 4. 三种遍历方式
        System.out.println("\n--- 遍历方式对比 ---");
        System.out.print("  entrySet : ");
        for (Map.Entry<String, Integer> entry : freqMap.entrySet()) {
            System.out.print(entry.getKey() + "=" + entry.getValue() + " ");
        }
        System.out.print("\n  keySet   : ");
        for (String key : freqMap.keySet()) {
            System.out.print(key + " ");
        }
        System.out.print("\n  forEach  : ");
        freqMap.forEach((k, v) -> System.out.print(k + " "));
        System.out.println();

        // 5. 常用 Map 方法
        System.out.println("\n--- 常用方法 ---");
        System.out.println("  getOrDefault(\"Rust\", 0) = " + freqMap.getOrDefault("Rust", 0));
        System.out.println("  containsKey(\"Java\")     = " + freqMap.containsKey("Java"));
        System.out.println("  size()                   = " + freqMap.size());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

运行结果:

===== 单词频率统计器 =====
文本: Java is great Java is popular Python is also great

--- HashMap 统计结果(无序)---
  Java       : 2
  is         : 3
  great      : 2
  popular    : 1
  Python     : 1
  also       : 1

--- TreeMap 按字母排序 ---
  Java       : 2
  Python     : 1
  also       : 1
  great      : 2
  is         : 3
  popular    : 1

--- 按频率降序 ---
  is         : 3
  Java       : 2
  great      : 2
  Python     : 1
  popular    : 1
  also       : 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 11.4.5 训练题

  1. 实践题:使用 HashMap 统计一段文本中每个单词出现的次数,然后找出出现次数最多的前3个单词。分别用传统方式和 merge() 方法实现统计。

  2. 代码题:以下代码的输出是什么?请解释:

    HashMap<String, Integer> map = new HashMap<>();
    map.put("a", 1);
    map.put("a", 2);
    System.out.println(map.size());
    System.out.println(map.get("a"));
    
    1
    2
    3
    4
    5
  3. 思考题:为什么自定义对象作为 HashMap 的 key 时,必须同时重写 hashCode() 和 equals()?如果只重写其中一个会出现什么问题?

# 11.5 集合遍历

# 11.5.1 Iterator迭代器

ArrayList<String> list = new ArrayList<>(List.of("张三", "李四", "王五"));

Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String name = it.next();
    if ("李四".equals(name)) {
        it.remove();  // 安全地在遍历中删除元素
    }
}
1
2
3
4
5
6
7
8
9

# 11.5.2 for-each遍历

for (String name : list) {
    System.out.println(name);
    // 注意:for-each 中不能删除/添加元素,否则抛 ConcurrentModificationException
}
1
2
3
4

# 11.5.3 Stream流(JDK8+)

import java.util.List;
import java.util.stream.Collectors;

List<String> names = List.of("张三", "李四", "王五", "张伟", "李明");

// 过滤
List<String> zhangNames = names.stream()
    .filter(name -> name.startsWith("张"))
    .collect(Collectors.toList());
System.out.println(zhangNames);  // [张三, 张伟]

// 转换
List<Integer> lengths = names.stream()
    .map(String::length)
    .collect(Collectors.toList());

// 统计
long count = names.stream().filter(n -> n.length() == 2).count();

// 排序
List<String> sorted = names.stream()
    .sorted()
    .collect(Collectors.toList());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 11.5.4 综合案例:学生成绩分析器

本案例综合运用 Iterator、for-each 和 Stream 三种遍历方式处理同一数据集。

import java.util.*;
import java.util.stream.Collectors;

/**
 * 学生成绩分析器 —— 集合遍历综合案例
 * 知识点:Iterator安全删除、for-each遍历、Stream过滤/排序/统计/分组
 */
public class StudentAnalyzer {
    record Student(String name, int score) {}

    public static void main(String[] args) {
        ArrayList<Student> students = new ArrayList<>(List.of(
                new Student("张三", 92), new Student("李四", 55),
                new Student("王五", 78), new Student("赵六", 43),
                new Student("孙七", 88), new Student("周八", 61),
                new Student("吴九", 95), new Student("郑十", 70)
        ));

        System.out.println("===== 学生成绩分析器 =====\n");

        // 1. for-each 遍历显示
        System.out.println("--- 全部学生 ---");
        for (Student s : students) {
            System.out.printf("  %-4s %3d分%n", s.name(), s.score());
        }

        // 2. Iterator 安全删除不及格学生
        System.out.println("\n--- Iterator 删除不及格学生 ---");
        ArrayList<Student> copy = new ArrayList<>(students);
        Iterator<Student> it = copy.iterator();
        while (it.hasNext()) {
            Student s = it.next();
            if (s.score() < 60) {
                System.out.println("  移除: " + s.name() + " (" + s.score() + "分)");
                it.remove();  // 安全删除
            }
        }
        System.out.println("  剩余 " + copy.size() + " 人");

        // 3. Stream 综合分析(原始列表)
        System.out.println("\n--- Stream 分析 ---");

        // 按成绩降序
        System.out.println("成绩排名:");
        students.stream()
                .sorted(Comparator.comparingInt(Student::score).reversed())
                .forEach(s -> System.out.printf("  %-4s %3d分%n", s.name(), s.score()));

        // 统计
        IntSummaryStatistics stats = students.stream()
                .mapToInt(Student::score)
                .summaryStatistics();
        System.out.printf("\n统计: 最高=%d 最低=%d 平均=%.1f%n",
                stats.getMax(), stats.getMin(), stats.getAverage());

        // 按等级分组
        Map<String, List<Student>> groups = students.stream()
                .collect(Collectors.groupingBy(s -> {
                    if (s.score() >= 90) return "优秀";
                    if (s.score() >= 70) return "良好";
                    if (s.score() >= 60) return "及格";
                    return "不及格";
                }));

        System.out.println("\n等级分布:");
        groups.forEach((grade, list) -> {
            String names = list.stream().map(Student::name).collect(Collectors.joining(", "));
            System.out.printf("  %s(%d人): %s%n", grade, list.size(), names);
        });
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

运行结果:

===== 学生成绩分析器 =====

--- 全部学生 ---
  张三  92分
  李四  55分
  ...

--- Iterator 删除不及格学生 ---
  移除: 李四 (55分)
  移除: 赵六 (43分)
  剩余 6 人

--- Stream 分析 ---
成绩排名:
  吴九  95分
  张三  92分
  孙七  88分
  ...

统计: 最高=95 最低=43 平均=72.8

等级分布:
  优秀(2人): 张三, 吴九
  良好(2人): 王五, 郑十
  及格(2人): 孙七, 周八
  不及格(2人): 李四, 赵六
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 11.5.5 训练题

  1. 实践题:使用 Iterator 遍历一个 ArrayList<Integer>,删除其中所有偶数元素。解释为什么不能用 for-each 完成同样的操作。

  2. 代码题:使用 Stream API 完成以下操作——给定一个学生列表(姓名、成绩),按成绩降序排列,筛选成绩>=60的学生,计算平均分。

  3. 思考题:Stream 的"惰性求值"是什么意思?以下代码中 filter 会对所有 5 个元素执行吗?

    List.of(1,2,3,4,5).stream()
        .filter(n -> { System.out.println("filter: " + n); return n > 3; })
        .findFirst();
    
    1
    2
    3

# 11.6 Collections工具类

import java.util.Collections;
import java.util.ArrayList;

ArrayList<Integer> list = new ArrayList<>(List.of(5, 3, 1, 4, 2));

Collections.sort(list);             // 排序:[1, 2, 3, 4, 5]
Collections.reverse(list);          // 反转:[5, 4, 3, 2, 1]
Collections.shuffle(list);          // 随机打乱
int max = Collections.max(list);    // 最大值
int min = Collections.min(list);    // 最小值
Collections.swap(list, 0, 1);       // 交换两个位置的元素

// 创建不可修改的集合
List<String> unmodifiable = Collections.unmodifiableList(list);

// JDK 9+ 更简洁的不可变集合
List<String> immutable = List.of("a", "b", "c");
Map<String, Integer> immutableMap = Map.of("a", 1, "b", 2);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 11.6.1 综合案例:集合工具箱

本案例演示 Collections 工具类的常用方法。

import java.util.*;

/**
 * 集合工具箱 —— Collections工具类综合案例
 * 知识点:sort、reverse、shuffle、max/min、binarySearch、unmodifiable、frequency
 */
public class CollectionToolbox {
    public static void main(String[] args) {
        System.out.println("===== Collections 工具箱 =====\n");

        ArrayList<Integer> nums = new ArrayList<>(List.of(38, 15, 92, 27, 64, 51));
        System.out.println("原始: " + nums);

        // 排序
        Collections.sort(nums);
        System.out.println("排序: " + nums);

        // 二分查找(必须先排序)
        int idx = Collections.binarySearch(nums, 51);
        System.out.println("查找51: 索引=" + idx);

        // 反转
        Collections.reverse(nums);
        System.out.println("反转: " + nums);

        // 最大最小
        System.out.println("最大: " + Collections.max(nums));
        System.out.println("最小: " + Collections.min(nums));

        // 频率
        ArrayList<String> words = new ArrayList<>(List.of("a", "b", "a", "c", "a"));
        System.out.println("\n单词列表: " + words);
        System.out.println("'a'出现: " + Collections.frequency(words, "a") + "次");

        // 替换和填充
        Collections.replaceAll(words, "a", "A");
        System.out.println("替换a→A: " + words);

        // 不可修改集合
        List<String> readOnly = Collections.unmodifiableList(words);
        try {
            readOnly.add("d");
        } catch (UnsupportedOperationException e) {
            System.out.println("\n不可修改集合: 添加操作被拒绝 ✓");
        }

        // 打乱
        Collections.shuffle(nums);
        System.out.println("\n打乱: " + nums);

        // 交换
        Collections.swap(nums, 0, nums.size() - 1);
        System.out.println("首尾交换: " + nums);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

# 11.6.2 训练题

  1. 实践题:使用 Collections 工具类完成以下操作——给定一个 ArrayList<Integer>,先排序,再反转,然后使用 binarySearch 查找指定元素。注意 binarySearch 的前置条件是什么。

  2. 代码题:以下代码运行后会发生什么?为什么?

    List<String> list = Collections.unmodifiableList(new ArrayList<>(List.of("a", "b")));
    list.add("c");
    
    1
    2
  3. 思考题:Collections.unmodifiableList() 和 List.of() 都能创建不可修改的集合,它们有什么区别?(提示:unmodifiableList 返回的是原集合的视图,原集合修改后视图也会变化;List.of() 是真正的不可变集合。)

上次更新: 2026/06/10, 11:13:41
异常处理
IO流和File

← 异常处理 IO流和File→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式