集合框架:装数据的正确姿势
难度:中级 | 预计时间:45 分钟 | 前置:面向对象(上)
📖 数组不够用了
故事背景
你用数组存用户列表——User[] users = new User[100]。新用户注册,数组满了,你得手动扩容。删一个用户,中间留个洞,遍历时还要判空。想找"所有 VIP 用户"——自己写循环。ArrayList、HashMap、HashSet 就是来救你的。Java 的集合框架(Collections Framework)是一套设计精良的数据结构库,学完你会发现 JS 的 Array/Object 是多么"粗糙"。
💻 代码演示
拿出你的 JS 经验——下面是对照表式演示:
import java.util.*;
// JS: const users = [] → Java: List<User> users = new ArrayList<>();
// JS: users.push(user) → users.add(user);
// JS: users.find(u => u.vip) → users.stream().filter(User::isVip).findFirst()
// JS: users.map(u => u.name) → users.stream().map(User::getName).toList()
// ===== 常用集合演示 =====
public class CollectionDemo {
public static void main(String[] args) {
// ArrayList — 有序,可重复,按索引访问快
List<String> names = new ArrayList<>();
names.add("张三");
names.add("李四");
names.add("张三"); // 可以重复
System.out.println("第1个: " + names.get(0)); // 张三
// HashSet — 无序,不可重复,查"是否存在"快
Set<String> uniqueNames = new HashSet<>(names);
System.out.println("去重后: " + uniqueNames); // [李四, 张三]
// HashMap — 键值对,通过 key 快速查找 value
Map<String, Integer> scores = new HashMap<>();
scores.put("张三", 95);
scores.put("李四", 87);
System.out.println("张三的分数: " + scores.get("张三")); // 95
// Stream API — 声明式数据处理(类似 JS 的链式操作)
List<Integer> highScores = scores.values().stream()
.filter(s -> s >= 90)
.sorted()
.toList();
System.out.println("90分以上: " + highScores); // [95]
}
}运行输出:
第1个: 张三
去重后: [李四, 张三]
张三的分数: 95
90分以上: [95]🎯 三个核心问题
这是什么?
Java 集合框架分为两大接口体系:Collection(List / Set / Queue)和 Map(键值对)。核心实现类:ArrayList(动态数组)、LinkedList(双向链表)、HashSet(哈希集)、TreeSet(排序集)、HashMap(哈希表)、TreeMap(排序树)。Java 8+ 提供了 Stream API 做函数式数据处理。
为什么需要它?
为什么有这么多实现?不同场景需要不同性能特点:ArrayList 随机访问 O(1) 但中间插入 O(n);LinkedList 中间插入 O(1) 但随机访问 O(n);HashSet 去重和查存在 O(1);TreeSet 天然有序但 O(log n)。选择合适的集合,可能让程序快 100 倍。
如果没有它会怎样?
如果只有数组——每次扩容手动复制,查找需要自己写循环,去重要 O(n²) 的双重循环。集合框架把常见需求标准化,你不需要重新发明轮子,而且经过几十年的优化和测试,性能和正确性都有保障。
📝 原理讲解
前端到 Java 集合速查表:
JS Java
─────────────────────────────────────────────
Array (动态数组) ArrayList
Set HashSet
Map / Object HashMap
Array.filter/map/reduce Stream API
Array.find Optional.orElse + Stream选型决策树:
需要有序 + 可重复 →
ArrayList需要去重 →
HashSet需要去重 + 排序 →
TreeSet需要键值对查找 →
HashMap需要键值对 + 有序 →
LinkedHashMap或TreeMap需要线程安全 →
ConcurrentHashMap(进阶)
注意:遍历 HashMap 时不要在循环里删除元素——用 Iterator 或 removeIf。
🎨 生活类比
类比理解
ArrayList 是排队——有顺序、可以有重复的人、按编号找人快("第 5 个往前走")。 HashSet 是花名册——无序、每个人只签一次名、查"来了没"极快(实际上是通过哈希值定位,不看名字)。 HashMap 是词典——通过 key(单词)直接翻到 value(解释),不用从头扫到尾。一本词典可以几秒找到某个词,从头翻可能要十几分钟。 Stream API 是流水线——每个操作是一道工序(过滤、排序、转换),数据在流水线上走一遍,出来就是成品。
✏️ 动手练习
练习 1
写一个方法 countWords(String text),返回 Map<String, Integer> 统计每个单词出现次数。
<details> <summary>💡 查看提示</summary>
用 text.split("\\s+") 分词,遍历单词,map.put(word, map.getOrDefault(word, 0) + 1)。
</details>
练习 2
用 Stream API:从一个 List<Integer> 中筛选出所有偶数、去重、排序、取前 5 个。
<details> <summary>💡 查看提示</summary>
链式调用:.stream().filter(n -> n % 2 == 0).distinct().sorted().limit(5).toList()
</details>
练习 3
实现一个简单任务优先级队列:Task 有 name 和 priority,用 PriorityQueue 让高优先级的先出队。
<details> <summary>💡 查看提示</summary>
PriorityQueue 默认是小顶堆。需要自定义比较器:new PriorityQueue<>((a, b) -> b.priority - a.priority) 实现大顶堆。
</details>
✅ 自检站
<details> <summary><strong>ArrayList 和 LinkedList 各适合什么场景?</strong></summary>
ArrayList 底层是数组,随机访问(get)O(1),末尾插入 O(1),中间插入/删除 O(n)(需要移动后续元素)。适合"读多写少、只在末尾添加"的场景。LinkedList 底层是双向链表,随机访问 O(n),头尾插入 O(1)。适合"频繁在头部插入/删除"的场景(如队列)。绝大多数场景 ArrayList 就够用了。
</details>
<details> <summary><strong>HashMap 的原理是什么?</strong></summary>
HashMap 基于哈希表。存入时对 key 调用 hashCode() 得到哈希值,映射到内部数组的索引位置。取时同样计算哈希值直接定位。理想情况下 O(1)。如果两个 key 哈希碰撞(映射到同一位置),该位置用链表/红黑树存储多个 entry。Java 8+ 当碰撞链长度超过 8 时转为红黑树,保证最坏 O(log n)。
</details>