0
0
Java 入门指南:第三章 - Java 集合框架完全指南
2026-09-07
文章摘要
|
承接前两章的基础语法和面向对象内容,本章将深入讲解 Java 集合框架(Java Collections Framework)。集合是实际开发中使用频率最高的工具之一,掌握好集合框架,你的编程效率将提升一个档次。
第三章:Java 集合框架(Collections Framework)
3.1 集合框架概述
3.1.1 为什么需要集合框架?
在编程中,我们经常需要存储和操作一组数据。虽然数组可以做到,但它有以下限制:
- 长度固定,不能动态扩容
- 只能存储同一类型的数据
- 操作方法有限(没有现成的排序、查找等)
集合框架(Collections Framework)就是为了解决这些问题而设计的统一架构。
3.1.2 集合框架的核心接口层次结构
Iterable (根接口)
└── Collection (集合根接口)
├── List (有序、可重复)
│ ├── ArrayList
│ ├── LinkedList
│ ├── Vector (线程安全)
│ └── Stack
├── Set (无序、不可重复)
│ ├── HashSet
│ ├── LinkedHashSet
│ └── TreeSet
└── Queue (队列)
├── LinkedList
├── PriorityQueue
└── ArrayDeque
Map (独立的根接口,不是 Collection 的子接口)
├── HashMap
├── LinkedHashMap
├── TreeMap
├── Hashtable (线程安全)
└── ConcurrentHashMap (并发优化)
3.1.3 集合框架的三大分类
| 分类 | 特点 | 主要实现类 |
|---|---|---|
| List | 有序、可重复、有索引 | ArrayList, LinkedList, Vector |
| Set | 无序、不可重复 | HashSet, LinkedHashSet, TreeSet |
| Map | 键值对(key-value)存储 | HashMap, LinkedHashMap, TreeMap |
3.2 Collection 接口:所有集合的根
3.2.1 Collection 的通用方法
import java.util.*;
public class CollectionBasics {
public static void main(String[] args) {
// 使用多态创建集合
Collection<String> collection = new ArrayList<>();
// 1. 添加元素
collection.add("Java");
collection.add("Python");
collection.add("Go");
System.out.println("添加后:" + collection); // [Java, Python, Go]
// 2. 批量添加
Collection<String> more = Arrays.asList("C++", "JavaScript");
collection.addAll(more);
System.out.println("批量添加后:" + collection);
// 3. 删除元素
collection.remove("Go");
System.out.println("删除 Go 后:" + collection);
// 4. 判断包含
System.out.println("是否包含 Java?" + collection.contains("Java"));
System.out.println("是否包含所有?" + collection.containsAll(more));
// 5. 大小判断
System.out.println("集合大小:" + collection.size());
System.out.println("是否为空:" + collection.isEmpty());
// 6. 转换为数组
Object[] array = collection.toArray();
String[] stringArray = collection.toArray(new String[0]);
// 7. 清除所有元素
collection.clear();
System.out.println("清空后:" + collection);
}
}
3.2.2 遍历集合的四种方式
public class CollectionTraversal {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("苹果", "香蕉", "橙子", "葡萄");
// 方式1:普通 for 循环(仅适用于 List)
System.out.println("=== 普通 for 循环 ===");
for (int i = 0; i < fruits.size(); i++) {
System.out.println(fruits.get(i));
}
// 方式2:增强 for 循环(推荐)
System.out.println("=== 增强 for 循环 ===");
for (String fruit : fruits) {
System.out.println(fruit);
}
// 方式3:迭代器(Iterator)
System.out.println("=== 迭代器 ===");
Iterator<String> iterator = fruits.iterator();
while (iterator.hasNext()) {
String fruit = iterator.next();
System.out.println(fruit);
// 可以在遍历时删除元素
// iterator.remove();
}
// 方式4:Lambda 表达式(Java 8+)
System.out.println("=== Lambda 表达式 ===");
fruits.forEach(fruit -> System.out.println(fruit));
// 方式4b:方法引用
System.out.println("=== 方法引用 ===");
fruits.forEach(System.out::println);
}
}
3.3 List 接口详解
3.3.1 ArrayList:最常用的列表实现
特点:
- 底层是数组,查询快(O(1))
- 增删慢(O(n)),特别是中间位置
- 线程不安全
- 支持随机访问(实现了 RandomAccess 接口)
public class ArrayListDemo {
public static void main(String[] args) {
// 创建 ArrayList
ArrayList<String> list = new ArrayList<>();
// 指定初始容量(避免扩容开销)
ArrayList<String> listWithCapacity = new ArrayList<>(100);
// 添加元素
list.add("A");
list.add("B");
list.add("C");
list.add(1, "X"); // 在索引 1 插入
System.out.println(list); // [A, X, B, C]
// 获取元素
String element = list.get(2);
System.out.println("索引2的元素:" + element); // B
// 修改元素
list.set(1, "Y");
System.out.println("修改后:" + list); // [A, Y, B, C]
// 删除元素(按索引)
String removed = list.remove(0);
System.out.println("删除的元素:" + removed);
System.out.println("删除后:" + list); // [Y, B, C]
// 删除元素(按对象)
list.remove("B");
System.out.println("删除 B 后:" + list); // [Y, C]
// 查找元素索引
list.add("C");
System.out.println("C 首次出现位置:" + list.indexOf("C"));
System.out.println("C 最后出现位置:" + list.lastIndexOf("C"));
// 子列表(视图,修改会影响原列表)
List<String> subList = list.subList(0, 1);
System.out.println("子列表:" + subList);
subList.set(0, "Z");
System.out.println("修改子列表后原列表:" + list); // [Z, C, C]
// 排序(自然顺序)
list.sort(null);
System.out.println("排序后:" + list); // [C, C, Z]
// 自定义排序
list.sort((a, b) -> b.compareTo(a)); // 降序
System.out.println("降序:" + list); // [Z, C, C]
}
}
3.3.2 深入理解 ArrayList 的扩容机制
public class ArrayListCapacity {
public static void main(String[] args) throws Exception {
// 使用反射查看底层数组大小
ArrayList<Integer> list = new ArrayList<>();
System.out.println("初始容量:10(默认)");
System.out.println("实际大小:0");
// 添加 11 个元素,触发扩容
for (int i = 0; i < 11; i++) {
list.add(i);
System.out.println("添加第 " + (i+1) + " 个元素后,size = " + list.size());
}
// 扩容规则:新容量 = 旧容量 * 1.5
// 10 -> 15 -> 22 -> 33 -> ...
// 可以使用 ensureCapacity(int) 预分配容量
// 手动缩减容量到当前实际大小
list.trimToSize();
System.out.println("执行 trimToSize() 后,底层数组大小变为 " + list.size());
}
}
3.3.3 LinkedList:双向链表的实现
特点:
- 底层是双向链表
- 增删快(O(1)),特别是首尾操作
- 查询慢(O(n))
- 实现了 Queue 和 Deque 接口,可以当队列使用
public class LinkedListDemo {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
// 作为 List 使用
list.add("Java");
list.add("Python");
list.addFirst("Go"); // 插入到头部
list.addLast("Rust"); // 插入到尾部
System.out.println(list); // [Go, Java, Python, Rust]
// 作为 Queue 使用(FIFO)
Queue<String> queue = list;
queue.offer("Swift"); // 入队
System.out.println("队首元素:" + queue.peek()); // 查看不移除
System.out.println("出队元素:" + queue.poll()); // 移除并返回
System.out.println("出队后:" + queue);
// 作为 Deque 使用(双端队列)
Deque<String> deque = list;
deque.addFirst("前端");
deque.addLast("后端");
System.out.println("双端队列:" + deque);
// 特有方法
System.out.println("获取第一个:" + list.getFirst());
System.out.println("获取最后一个:" + list.getLast());
System.out.println("移除第一个:" + list.removeFirst());
System.out.println("移除最后一个:" + list.removeLast());
}
}
3.3.4 ArrayList vs LinkedList 性能对比
public class ListPerformance {
public static void main(String[] args) {
final int SIZE = 100000;
// 1. 插入性能测试
long startTime = System.nanoTime();
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < SIZE; i++) {
arrayList.add(i);
}
long arrayListTime = System.nanoTime() - startTime;
startTime = System.nanoTime();
LinkedList<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < SIZE; i++) {
linkedList.add(i);
}
long linkedListTime = System.nanoTime() - startTime;
System.out.println("尾部插入耗时:");
System.out.println("ArrayList: " + arrayListTime / 1_000_000 + " ms");
System.out.println("LinkedList: " + linkedListTime / 1_000_000 + " ms");
// 2. 中间插入测试
startTime = System.nanoTime();
for (int i = 0; i < 1000; i++) {
arrayList.add(SIZE / 2, i);
}
arrayListTime = System.nanoTime() - startTime;
startTime = System.nanoTime();
for (int i = 0; i < 1000; i++) {
linkedList.add(SIZE / 2, i);
}
linkedListTime = System.nanoTime() - startTime;
System.out.println("\n中间插入 1000 次耗时:");
System.out.println("ArrayList: " + arrayListTime / 1_000_000 + " ms");
System.out.println("LinkedList: " + linkedListTime / 1_000_000 + " ms");
// 3. 随机访问测试
startTime = System.nanoTime();
for (int i = 0; i < SIZE; i++) {
arrayList.get(i);
}
arrayListTime = System.nanoTime() - startTime;
startTime = System.nanoTime();
for (int i = 0; i < SIZE; i++) {
linkedList.get(i);
}
linkedListTime = System.nanoTime() - startTime;
System.out.println("\n随机访问 " + SIZE + " 次耗时:");
System.out.println("ArrayList: " + arrayListTime / 1_000_000 + " ms");
System.out.println("LinkedList: " + linkedListTime / 1_000_000 + " ms");
}
}
3.3.5 Vector 和 Stack(线程安全的遗留类)
public class VectorDemo {
public static void main(String[] args) {
// Vector:线程安全的 ArrayList(同步)
Vector<String> vector = new Vector<>();
vector.add("A");
vector.add("B");
vector.add("C");
// Vector 特有的方法
System.out.println("容量:" + vector.capacity()); // 默认 10
System.out.println("大小:" + vector.size());
// Stack:继承自 Vector,LIFO(后进先出)
Stack<String> stack = new Stack<>();
stack.push("第一层");
stack.push("第二层");
stack.push("第三层");
System.out.println("栈顶元素:" + stack.peek()); // 查看不移除
System.out.println("弹出元素:" + stack.pop()); // 弹出
System.out.println("弹出后栈顶:" + stack.peek());
System.out.println("栈是否为空:" + stack.empty());
System.out.println("元素位置:" + stack.search("第一层")); // 从1开始计数
// 注意:Vector 和 Stack 是遗留类,建议使用 ArrayList 和 Deque 替代
// 推荐使用 Collections.synchronizedList() 或 Concurrent 包
// 使用 Deque 替代 Stack
Deque<String> dequeStack = new ArrayDeque<>();
dequeStack.push("A");
dequeStack.push("B");
System.out.println("Deque 作为栈:" + dequeStack.pop()); // B
}
}
3.4 Set 接口详解
3.4.1 HashSet:最常用的 Set 实现
特点:
- 底层是 HashMap
- 元素无序(不保证顺序)
- 不允许重复(通过 equals() 和 hashCode() 判断)
- 允许 null 值
- 时间复杂度 O(1)
public class HashSetDemo {
public static void main(String[] args) {
// 创建 HashSet
Set<String> set = new HashSet<>();
// 添加元素(重复元素不会被添加)
set.add("Java");
set.add("Python");
set.add("Go");
set.add("Java"); // 重复,不会添加
System.out.println("集合内容:" + set); // 顺序不确定,如 [Java, Go, Python]
// 批量添加
set.addAll(Arrays.asList("C++", "JavaScript"));
System.out.println("批量添加后:" + set);
// 删除元素
set.remove("Go");
System.out.println("删除 Go 后:" + set);
// 判断包含
System.out.println("是否包含 Java?" + set.contains("Java"));
// 大小
System.out.println("集合大小:" + set.size());
// 遍历
set.forEach(System.out::println);
}
}
3.4.2 自定义对象在 Set 中的去重
import java.util.*;
class Student {
private String id;
private String name;
private int age;
public Student(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
// 必须重写 equals() 和 hashCode()
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(id, student.id) &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name, age);
}
@Override
public String toString() {
return "Student{id='" + id + "', name='" + name + "', age=" + age + "}";
}
}
public class CustomObjectSet {
public static void main(String[] args) {
Set<Student> students = new HashSet<>();
// 添加学生对象
students.add(new Student("001", "张三", 20));
students.add(new Student("002", "李四", 22));
students.add(new Student("001", "张三", 20)); // 重复(根据内容判断)
System.out.println("学生数量:" + students.size()); // 2
students.forEach(System.out::println);
}
}
3.4.3 LinkedHashSet:保持插入顺序
public class LinkedHashSetDemo {
public static void main(String[] args) {
// LinkedHashSet 维护了一个双向链表来记录插入顺序
Set<String> linkedSet = new LinkedHashSet<>();
linkedSet.add("A");
linkedSet.add("B");
linkedSet.add("C");
linkedSet.add("D");
linkedSet.add("A"); // 重复,不影响顺序
System.out.println("LinkedHashSet(保持插入顺序):");
linkedSet.forEach(System.out::println);
// 输出:A, B, C, D
// 对比 HashSet(顺序不确定)
Set<String> hashSet = new HashSet<>();
hashSet.addAll(linkedSet);
System.out.println("\nHashSet(顺序不确定):");
hashSet.forEach(System.out::println);
}
}
3.4.4 TreeSet:排序的 Set
特点:
- 底层是 TreeMap(红黑树)
- 元素自动排序(自然顺序或自定义比较器)
- 不允许重复
- 时间复杂度 O(log n)
public class TreeSetDemo {
public static void main(String[] args) {
// 自然排序(必须实现 Comparable)
Set<Integer> numbers = new TreeSet<>();
numbers.add(5);
numbers.add(1);
numbers.add(8);
numbers.add(3);
numbers.add(8); // 重复,忽略
System.out.println("自然排序:" + numbers); // [1, 3, 5, 8]
// 自定义排序(降序)
Set<Integer> descNumbers = new TreeSet<>((a, b) -> b - a);
descNumbers.addAll(numbers);
System.out.println("降序排序:" + descNumbers); // [8, 5, 3, 1]
// 自定义对象排序(必须实现 Comparable)
Set<Person> people = new TreeSet<>();
people.add(new Person("张三", 25));
people.add(new Person("李四", 30));
people.add(new Person("王五", 22));
System.out.println("按年龄排序:");
people.forEach(System.out::println);
// TreeSet 的特殊方法
TreeSet<Integer> treeSet = new TreeSet<>(numbers);
System.out.println("第一个元素:" + treeSet.first());
System.out.println("最后一个元素:" + treeSet.last());
System.out.println("小于 5 的最大元素:" + treeSet.lower(5)); // 3
System.out.println("小于等于 5 的最大元素:" + treeSet.floor(5)); // 5
System.out.println("大于 3 的最小元素:" + treeSet.higher(3)); // 5
System.out.println("大于等于 3 的最小元素:" + treeSet.ceiling(3)); // 3
// 子集操作
Set<Integer> subSet = treeSet.subSet(2, true, 6, true); // [2, 6]
System.out.println("子集:" + subSet);
}
}
// 必须实现 Comparable 接口
class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Person other) {
return this.age - other.age; // 按年龄升序
// return other.age - this.age; // 按年龄降序
}
@Override
public String toString() {
return name + "(" + age + ")";
}
}
3.4.5 HashSet vs TreeSet vs LinkedHashSet 对比
| 特性 | HashSet | LinkedHashSet | TreeSet |
|---|---|---|---|
| 底层结构 | 哈希表(HashMap) | 哈希表 + 双向链表 | 红黑树(TreeMap) |
| 顺序 | 无序 | 插入顺序 | 排序顺序 |
| 时间复杂度 | O(1) | O(1) | O(log n) |
| 是否允许 null | 允许(最多一个) | 允许 | 不允许(需要比较) |
| 使用场景 | 一般去重 | 需要保持插入顺序 | 需要排序 |
3.5 Map 接口详解
3.5.1 HashMap:最常用的 Map 实现
特点:
- 底层是哈希表(数组 + 链表/红黑树)
- 键(Key)不可重复
- 允许一个 null 键和多个 null 值
- 线程不安全
- 时间复杂度 O(1)
public class HashMapDemo {
public static void main(String[] args) {
// 创建 HashMap
Map<String, Integer> map = new HashMap<>();
// 1. 添加键值对
map.put("Java", 95);
map.put("Python", 88);
map.put("Go", 92);
map.put("Java", 98); // 覆盖之前的 value
System.out.println("Map 内容:" + map); // {Java=98, Go=92, Python=88}
// 2. putIfAbsent:如果键不存在才添加
map.putIfAbsent("Rust", 85);
map.putIfAbsent("Java", 100); // 键已存在,不会覆盖
System.out.println("putIfAbsent 后:" + map);
// 3. 获取值
Integer javaScore = map.get("Java");
System.out.println("Java 的分数:" + javaScore);
System.out.println("不存在的键:" + map.get("PHP")); // null
// 4. getOrDefault:获取值,不存在返回默认值
System.out.println("PHP 的分数:" + map.getOrDefault("PHP", 0));
// 5. 删除
map.remove("Go");
System.out.println("删除 Go 后:" + map);
// 6. 判断键/值是否存在
System.out.println("是否包含键 Java?" + map.containsKey("Java"));
System.out.println("是否包含值 98?" + map.containsValue(98));
// 7. 遍历
System.out.println("\n=== 遍历 Entry ===");
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
System.out.println("\n=== 遍历键 ===");
map.keySet().forEach(key -> System.out.println(key));
System.out.println("\n=== 遍历值 ===");
map.values().forEach(value -> System.out.println(value));
// 8. 批量操作
Map<String, Integer> other = new HashMap<>();
other.put("C++", 90);
other.put("JavaScript", 85);
map.putAll(other);
System.out.println("批量添加后:" + map);
// 9. compute 操作
map.compute("Java", (key, val) -> val + 5);
System.out.println("Java 加分后:" + map.get("Java"));
map.computeIfAbsent("C#", key -> 80);
System.out.println("C# 添加后:" + map.get("C#"));
map.computeIfPresent("Python", (key, val) -> val + 2);
System.out.println("Python 加分后:" + map.get("Python"));
}
}
3.5.2 深入理解 HashMap 的底层实现
public class HashMapDeepDive {
public static void main(String[] args) {
// 1. 初始容量和负载因子
// 默认初始容量 16,负载因子 0.75
Map<String, String> defaultMap = new HashMap<>();
// 指定初始容量(推荐:容量 = 所需大小 / 0.75)
Map<String, String> customMap = new HashMap<>(32);
// 指定容量和负载因子
Map<String, String> customMap2 = new HashMap<>(16, 0.8f);
// 2. 扩容时机:size > capacity * loadFactor
// 例如:16 * 0.75 = 12,当元素超过 12 个时扩容到 32
// 3. 哈希冲突解决
// - Java 8 之前:拉链法(链表)
// - Java 8 及以后:链表长度 > 8 且数组长度 >= 64 时转为红黑树
// 4. 键的要求
// 键必须正确重写 hashCode() 和 equals()
// 不可变对象作为键更安全(如 String, Integer)
// 5. 性能优化
Map<String, String> optimized = new HashMap<>();
// 预分配容量,避免频繁扩容
int expectedSize = 100;
int capacity = (int) (expectedSize / 0.75) + 1;
Map<String, String> preAllocated = new HashMap<>(capacity);
}
}
3.5.3 LinkedHashMap:保持插入顺序的 Map
public class LinkedHashMapDemo {
public static void main(String[] args) {
// 默认按照插入顺序
Map<String, String> linkedMap = new LinkedHashMap<>();
linkedMap.put("A", "Apple");
linkedMap.put("B", "Banana");
linkedMap.put("C", "Cherry");
linkedMap.put("D", "Durian");
System.out.println("LinkedHashMap(插入顺序):");
linkedMap.forEach((k, v) -> System.out.println(k + " -> " + v));
// 访问顺序模式(LRU 缓存实现)
// accessOrder = true 表示按访问顺序排序(最近访问的在最后)
LinkedHashMap<String, String> lruMap = new LinkedHashMap<>(16, 0.75f, true);
lruMap.put("A", "Apple");
lruMap.put("B", "Banana");
lruMap.put("C", "Cherry");
System.out.println("\n初始顺序:");
lruMap.forEach((k, v) -> System.out.println(k + " -> " + v));
// 访问元素 A
lruMap.get("A");
System.out.println("\n访问 A 后(A 移到最后):");
lruMap.forEach((k, v) -> System.out.println(k + " -> " + v));
// 访问元素 C
lruMap.get("C");
System.out.println("\n访问 C 后(C 移到最后):");
lruMap.forEach((k, v) -> System.out.println(k + " -> " + v));
// 实现 LRU 缓存
LRUCache<String, String> cache = new LRUCache<>(3);
cache.put("1", "One");
cache.put("2", "Two");
cache.put("3", "Three");
System.out.println("\n缓存满后:");
cache.forEach((k, v) -> System.out.println(k + " -> " + v));
cache.get("1");
cache.put("4", "Four"); // 会移除最久未使用的 "2"
System.out.println("\n添加 4 后(2 被移除):");
cache.forEach((k, v) -> System.out.println(k + " -> " + v));
}
}
// 自定义 LRU 缓存
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int maxSize;
public LRUCache(int maxSize) {
super(16, 0.75f, true);
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > maxSize;
}
}
3.5.4 TreeMap:排序的 Map
public class TreeMapDemo {
public static void main(String[] args) {
// 自然排序(键必须实现 Comparable)
TreeMap<String, Integer> treeMap = new TreeMap<>();
treeMap.put("Banana", 2);
treeMap.put("Apple", 5);
treeMap.put("Cherry", 3);
treeMap.put("Date", 1);
System.out.println("TreeMap(按键排序):");
treeMap.forEach((k, v) -> System.out.println(k + " -> " + v));
// 自定义排序(降序)
TreeMap<String, Integer> descMap = new TreeMap<>((a, b) -> b.compareTo(a));
descMap.putAll(treeMap);
System.out.println("\n降序排列:");
descMap.forEach((k, v) -> System.out.println(k + " -> " + v));
// TreeMap 的特殊方法
TreeMap<Integer, String> map = new TreeMap<>();
map.put(10, "Ten");
map.put(20, "Twenty");
map.put(30, "Thirty");
map.put(40, "Forty");
map.put(50, "Fifty");
System.out.println("\n=== 导航方法 ===");
System.out.println("最小键:" + map.firstKey() + " -> " + map.firstEntry());
System.out.println("最大键:" + map.lastKey() + " -> " + map.lastEntry());
System.out.println("小于 30 的最大键:" + map.lowerKey(30) + " -> " + map.lowerEntry(30));
System.out.println("小于等于 30 的最大键:" + map.floorKey(30) + " -> " + map.floorEntry(30));
System.out.println("大于 30 的最小键:" + map.higherKey(30) + " -> " + map.higherEntry(30));
System.out.println("大于等于 30 的最小键:" + map.ceilingKey(30) + " -> " + map.ceilingEntry(30));
// 子 Map
System.out.println("\n键在 20-40 之间的子 Map:");
map.subMap(20, true, 40, true).forEach((k, v) -> System.out.println(k + " -> " + v));
}
}
3.5.5 Hashtable 与 ConcurrentHashMap
import java.util.concurrent.*;
public class MapThreadSafety {
public static void main(String[] args) {
// 1. Hashtable:线程安全(全表锁),遗留类,不推荐
Hashtable<String, String> hashtable = new Hashtable<>();
hashtable.put("key", "value");
// hashtable.put(null, "value"); // 不允许 null 键
// hashtable.put("key", null); // 不允许 null 值
// 2. Collections.synchronizedMap:包装成线程安全的 Map
Map<String, String> synchronizedMap = Collections.synchronizedMap(new HashMap<>());
// 使用时需要外部同步
synchronized (synchronizedMap) {
synchronizedMap.put("key", "value");
}
// 3. ConcurrentHashMap:推荐使用(分段锁,高性能)
ConcurrentHashMap<String, String> concurrentMap = new ConcurrentHashMap<>();
concurrentMap.put("key", "value");
concurrentMap.putIfAbsent("key2", "value2");
// ConcurrentHashMap 的原子操作
concurrentMap.compute("key", (k, v) -> v + " updated");
String result = concurrentMap.get("key");
System.out.println("ConcurrentHashMap 结果:" + result);
// 4. 性能对比(单线程场景 HashMap 更快)
// 多线程并发场景 ConcurrentHashMap 优于 Hashtable
// 5. 使用 ConcurrentHashMap 的推荐方式
ConcurrentHashMap<String, Integer> scores = new ConcurrentHashMap<>();
// 原子更新
scores.compute("player1", (key, val) -> val == null ? 1 : val + 1);
System.out.println("player1 分数:" + scores.get("player1"));
// 或者使用累加操作
scores.merge("player2", 1, Integer::sum);
scores.merge("player2", 1, Integer::sum);
System.out.println("player2 分数:" + scores.get("player2")); // 2
}
}
3.6 Queue 和 Deque 接口
3.6.1 Queue 接口:队列(FIFO)
public class QueueDemo {
public static void main(String[] args) {
// 1. LinkedList 作为队列
Queue<String> queue = new LinkedList<>();
// 入队操作
queue.offer("任务1");
queue.offer("任务2");
queue.offer("任务3");
System.out.println("队列:" + queue); // [任务1, 任务2, 任务3]
// 查看队首(不移除)
System.out.println("队首元素:" + queue.peek()); // 任务1
// 出队操作
System.out.println("处理:" + queue.poll()); // 任务1
System.out.println("处理:" + queue.poll()); // 任务2
System.out.println("处理后的队列:" + queue); // [任务3]
// 2. PriorityQueue:优先级队列(自然顺序或自定义比较器)
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
priorityQueue.offer(10);
priorityQueue.offer(5);
priorityQueue.offer(8);
priorityQueue.offer(1);
System.out.println("\n优先级队列(最小堆):");
while (!priorityQueue.isEmpty()) {
System.out.println("出队:" + priorityQueue.poll());
}
// 输出:1, 5, 8, 10
// 最大堆(自定义比较器)
PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
maxHeap.offer(10);
maxHeap.offer(5);
maxHeap.offer(8);
maxHeap.offer(1);
System.out.println("\n最大堆:");
while (!maxHeap.isEmpty()) {
System.out.println("出队:" + maxHeap.poll());
}
// 输出:10, 8, 5, 1
}
}
3.6.2 Deque 接口:双端队列
public class DequeDemo {
public static void main(String[] args) {
// ArrayDeque:推荐的高性能双端队列(优于 LinkedList)
Deque<String> deque = new ArrayDeque<>();
// 作为队列使用(FIFO)
deque.offer("A");
deque.offer("B");
deque.offer("C");
System.out.println("队列操作:" + deque.poll()); // A
// 作为栈使用(LIFO)
Deque<String> stack = new ArrayDeque<>();
stack.push("第一层");
stack.push("第二层");
stack.push("第三层");
System.out.println("\n栈操作:");
System.out.println("栈顶:" + stack.peek()); // 第三层
System.out.println("弹出:" + stack.pop()); // 第三层
System.out.println("弹出后栈顶:" + stack.peek()); // 第二层
// 双端操作
ArrayDeque<String> both = new ArrayDeque<>();
both.addFirst("左");
both.addLast("右");
both.addFirst("左左");
both.addLast("右右");
System.out.println("\n双端队列:" + both);
System.out.println("移除左侧:" + both.removeFirst()); // 左左
System.out.println("移除右侧:" + both.removeLast()); // 右右
System.out.println("剩余:" + both); // [左, 右]
}
}
3.7 集合工具类:Collections
import java.util.*;
public class CollectionsUtility {
public static void main(String[] args) {
// 1. 创建不可变集合
List<String> unmodifiableList = Collections.emptyList(); // 空列表
Set<String> singleton = Collections.singleton("单元素"); // 单元素集合
Map<String, String> emptyMap = Collections.emptyMap();
// 2. 线程安全的包装
List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
Map<String, String> synchronizedMap = Collections.synchronizedMap(new HashMap<>());
// 3. 排序
List<Integer> numbers = new ArrayList<>(Arrays.asList(5, 2, 8, 1, 9, 3));
Collections.sort(numbers); // 升序
System.out.println("升序:" + numbers);
Collections.sort(numbers, (a, b) -> b - a); // 降序
System.out.println("降序:" + numbers);
// 4. 二分查找
Collections.sort(numbers); // 必须先排序
int index = Collections.binarySearch(numbers, 5);
System.out.println("5 的位置:" + index);
// 5. 反转
Collections.reverse(numbers);
System.out.println("反转:" + numbers);
// 6. 打乱(随机排列)
Collections.shuffle(numbers);
System.out.println("打乱:" + numbers);
// 7. 最大值/最小值
System.out.println("最大值:" + Collections.max(numbers));
System.out.println("最小值:" + Collections.min(numbers));
// 8. 频率统计
List<String> words = Arrays.asList("a", "b", "a", "c", "a", "b");
System.out.println("'a' 出现次数:" + Collections.frequency(words, "a"));
// 9. 复制
List<Integer> dest = new ArrayList<>(Collections.nCopies(numbers.size(), 0));
Collections.copy(dest, numbers);
System.out.println("复制后的列表:" + dest);
// 10. 填充
Collections.fill(dest, 999);
System.out.println("填充后的列表:" + dest);
// 11. 旋转
Collections.rotate(numbers, 2);
System.out.println("旋转 2 位:" + numbers);
// 12. 交换
Collections.swap(numbers, 0, numbers.size() - 1);
System.out.println("交换首尾:" + numbers);
}
}
3.8 集合转换与数组互转
public class CollectionConversion {
public static void main(String[] args) {
// 1. 数组 → List
String[] array = {"A", "B", "C"};
List<String> list = Arrays.asList(array);
// 注意:Arrays.asList 返回的是固定大小的 List,不能修改大小
System.out.println("数组转 List:" + list);
// list.add("D"); // 抛出 UnsupportedOperationException
// 可修改的 List
List<String> mutableList = new ArrayList<>(Arrays.asList(array));
mutableList.add("D");
System.out.println("可修改的 List:" + mutableList);
// 2. List → 数组
List<Integer> numList = Arrays.asList(1, 2, 3);
Integer[] numArray = numList.toArray(new Integer[0]);
System.out.println("List 转数组:" + Arrays.toString(numArray));
// 3. 数组 → Set
Set<String> set = new HashSet<>(Arrays.asList(array));
System.out.println("数组转 Set:" + set);
// 4. Set → 数组
String[] setArray = set.toArray(new String[0]);
// 5. List → Set(去重)
List<String> duplicateList = Arrays.asList("A", "B", "A", "C", "B");
Set<String> uniqueSet = new HashSet<>(duplicateList);
System.out.println("List 去重:" + uniqueSet);
// 6. Set → List
List<String> listFromSet = new ArrayList<>(uniqueSet);
// 7. Map 的转换
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
// Map 的键集
Set<String> keySet = map.keySet();
// Map 的值集合
Collection<Integer> values = map.values();
// Map 的 Entry 集合
Set<Map.Entry<String, Integer>> entries = map.entrySet();
// Java 9+ 创建不可变集合
List<String> immutableList = List.of("A", "B", "C");
Set<String> immutableSet = Set.of("A", "B", "C");
Map<String, Integer> immutableMap = Map.of("A", 1, "B", 2, "C", 3);
// Java 10+ 使用 var(类型推断)
var varList = new ArrayList<String>();
varList.add("Hello");
varList.add("World");
System.out.println("var 类型推断:" + varList);
}
}
3.9 Stream API:集合的函数式编程
import java.util.*;
import java.util.stream.*;
public class StreamAPIDemo {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("张三", 30, 8000, "研发部"),
new Employee("李四", 25, 6000, "市场部"),
new Employee("王五", 35, 10000, "研发部"),
new Employee("赵六", 28, 7500, "财务部"),
new Employee("钱七", 32, 9000, "研发部")
);
// 1. 过滤
List<Employee> highSalary = employees.stream()
.filter(e -> e.getSalary() > 8000)
.collect(Collectors.toList());
System.out.println("薪资 > 8000 的员工:" + highSalary);
// 2. 映射(提取属性)
List<String> names = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
System.out.println("所有员工姓名:" + names);
// 3. 排序
List<Employee> sorted = employees.stream()
.sorted((a, b) -> b.getSalary() - a.getSalary())
.collect(Collectors.toList());
System.out.println("按薪资降序排序:");
sorted.forEach(System.out::println);
// 4. 聚合操作
double avgSalary = employees.stream()
.mapToDouble(Employee::getSalary)
.average()
.orElse(0);
System.out.println("平均薪资:" + avgSalary);
double totalSalary = employees.stream()
.mapToDouble(Employee::getSalary)
.sum();
System.out.println("薪资总和:" + totalSalary);
// 5. 分组
Map<String, List<Employee>> byDepartment = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
System.out.println("按部门分组:");
byDepartment.forEach((dept, empList) -> {
System.out.println(dept + ": " + empList.size() + "人");
});
// 6. 统计
IntSummaryStatistics stats = employees.stream()
.mapToInt(Employee::getSalary)
.summaryStatistics();
System.out.println("统计信息:");
System.out.println(" 最大值:" + stats.getMax());
System.out.println(" 最小值:" + stats.getMin());
System.out.println(" 平均值:" + stats.getAverage());
System.out.println(" 总和:" + stats.getSum());
System.out.println(" 计数:" + stats.getCount());
// 7. 查找
Optional<Employee> maxSalary = employees.stream()
.max(Comparator.comparing(Employee::getSalary));
maxSalary.ifPresent(e -> System.out.println("最高薪员工:" + e));
// 8. 匹配
boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000);
System.out.println("是否有薪资超过 10000 的员工:" + anyMatch);
boolean allMatch = employees.stream().allMatch(e -> e.getSalary() > 5000);
System.out.println("是否所有员工薪资都超过 5000:" + allMatch);
// 9. 归约
double total = employees.stream()
.map(Employee::getSalary)
.reduce(0.0, Double::sum);
System.out.println("使用 reduce 求和:" + total);
// 10. 链式操作
String result = employees.stream()
.filter(e -> e.getDepartment().equals("研发部"))
.map(Employee::getName)
.sorted()
.collect(Collectors.joining(", "));
System.out.println("研发部员工(按姓名排序):" + result);
// 11. 并行流(多线程处理)
long startTime = System.currentTimeMillis();
long count = employees.parallelStream()
.filter(e -> e.getAge() > 30)
.count();
long endTime = System.currentTimeMillis();
System.out.println("年龄 > 30 的员工数:" + count);
System.out.println("并行流耗时:" + (endTime - startTime) + "ms");
}
}
class Employee {
private String name;
private int age;
private double salary;
private String department;
public Employee(String name, int age, double salary, String department) {
this.name = name;
this.age = age;
this.salary = salary;
this.department = department;
}
// Getters
public String getName() { return name; }
public int getAge() { return age; }
public double getSalary() { return salary; }
public String getDepartment() { return department; }
@Override
public String toString() {
return String.format("%s(年龄:%d, 薪资:%.0f, 部门:%s)",
name, age, salary, department);
}
}
3.10 集合使用最佳实践
public class CollectionBestPractices {
public static void main(String[] args) {
// 1. 选择正确的集合类型
// - 需要快速查找:HashMap
// - 需要保持顺序:LinkedHashMap / LinkedHashSet
// - 需要排序:TreeMap / TreeSet
// - 需要快速随机访问:ArrayList
// - 需要频繁插入删除:LinkedList
// - 需要线程安全:ConcurrentHashMap / CopyOnWriteArrayList
// 2. 初始化时指定容量
int expectedSize = 1000;
Map<String, String> map = new HashMap<>((int) (expectedSize / 0.75) + 1);
// 3. 使用泛型确保类型安全
List<String> list = new ArrayList<>(); // 不允许添加非 String 类型
// 4. 使用不可变集合
List<String> immutable = List.of("A", "B", "C");
// 5. 避免在遍历时修改集合
List<String> items = new ArrayList<>(Arrays.asList("A", "B", "C"));
// 错误方式
// for (String item : items) {
// if ("B".equals(item)) items.remove(item); // ConcurrentModificationException
// }
// 正确方式1:使用 Iterator
Iterator<String> iterator = items.iterator();
while (iterator.hasNext()) {
if ("B".equals(iterator.next())) {
iterator.remove();
}
}
System.out.println("移除 B 后:" + items);
// 正确方式2:使用 removeIf
items.removeIf(item -> "C".equals(item));
System.out.println("移除 C 后:" + items);
// 6. 使用 isEmpty() 而不是 size() == 0
if (items.isEmpty()) {
System.out.println("集合为空");
}
// 7. 返回空集合而不是 null
List<String> getList() {
// 不要返回 null,而是返回空集合
return Collections.emptyList();
// 或者 return new ArrayList<>();
}
// 8. 使用 Optional 处理可能为 null 的值
Map<String, String> data = new HashMap<>();
Optional.ofNullable(data.get("key"))
.ifPresent(value -> System.out.println("值:" + value));
}
}
第四章:集合框架总结
4.1 选择指南速查表
| 需求场景 | 推荐集合 |
|---|---|
| 存储有序、可重复的数据 | ArrayList |
| 频繁在中间位置插入/删除 | LinkedList |
| 去重(无序) | HashSet |
| 去重(保持插入顺序) | LinkedHashSet |
| 去重(自动排序) | TreeSet |
| 键值对存储 | HashMap |
| 键值对(保持插入顺序) | LinkedHashMap |
| 键值对(自动排序) | TreeMap |
| 线程安全的键值对 | ConcurrentHashMap |
| 队列(FIFO) | ArrayDeque |
| 栈(LIFO) | ArrayDeque |
| 优先级队列 | PriorityQueue |
4.2 时间复杂度总结
| 集合 | 添加 | 删除 | 查找 | 访问 |
|---|---|---|---|---|
| ArrayList | O(1)* | O(n) | O(n) | O(1) |
| LinkedList | O(1) | O(1) | O(n) | O(n) |
| HashSet | O(1) | O(1) | O(1) | - |
| LinkedHashSet | O(1) | O(1) | O(1) | - |
| TreeSet | O(log n) | O(log n) | O(log n) | - |
| HashMap | O(1) | O(1) | O(1) | - |
| LinkedHashMap | O(1) | O(1) | O(1) | - |
| TreeMap | O(log n) | O(log n) | O(log n) | - |
*注:尾部添加为 O(1),扩容时可能为 O(n)
结语
本章详细介绍了 Java 集合框架的方方面面,从 List、Set、Map 三大体系,到 Queue、Deque 等队列结构,再到 Stream API 的函数式编程。集合框架是 Java 开发中最常用的工具,熟练掌握它们将让你的代码更加简洁、高效。
建议练习:
- 实现一个简单的学生管理系统(使用 HashMap 存储学生信息)
- 使用 TreeSet 实现一个自动排序的任务列表
- 使用 Stream API 处理大量数据(过滤、分组、统计)
- 实现一个 LRU 缓存(基于 LinkedHashMap)
下一章预告:我们将进入 Java IO 与文件操作,学习如何读写文件、处理流、以及 NIO 的新特性。敬请期待!📁
