集合框架与数据结构总览
学习目标
- 区分 List、Set、Queue/Deque、Map 的语义和常见实现。
- 根据索引、去重、顺序、优先级和并发边界选择集合。
- 通过细粒度页面快速查找 API,而不是在一个大表格里翻找。
核心知识点
专业术语
- List:有序、可重复、支持位置访问的序列。
- Set:强调元素唯一性,不提供索引语义。
- Queue/Deque:表达先进先出、双端进出或栈操作。
- Map:键到值的映射,键唯一。
- 不可变集合:创建后不能改变容器结构的集合结果。
白话解释与边界
因为按索引和顺序通常选 ArrayList;需要去重选 HashSet 或 LinkedHashSet;需要排序和范围查询选 TreeSet;需要任务先进先出或栈选 ArrayDeque;需要键值查找选 HashMap。平均 O(1) 不是顺序、线程安全或内存占用的保证,选择实现要先看业务语义。
详细页面:
常用用法
List:保存有序且可重复的元素
需要按位置访问元素并保留重复值时使用 List。
// 语义:需要按位置访问元素并保留重复值时使用 List。
import java.util.ArrayList;
import java.util.List;
List<String> values = new ArrayList<>(List.of("a", "a"));
System.out.println(values);
// 输出:[a, a]Set:保存不重复的元素
需要按相等性去重时使用 Set,是否保留顺序由具体实现决定。
// 语义:需要按相等性去重时使用 Set,是否保留顺序由具体实现决定。
import java.util.LinkedHashSet;
import java.util.Set;
Set<String> values = new LinkedHashSet<>(List.of("a", "b", "a"));
System.out.println(values);
// 输出:[a, b]Queue:按先进先出处理任务
需要按加入顺序消费任务时使用 Queue。
// 语义:需要按加入顺序消费任务时使用 Queue。
import java.util.ArrayDeque;
import java.util.Queue;
Queue<String> queue = new ArrayDeque<>();
queue.offer("task");
System.out.println(queue.poll());
// 输出:taskDeque:从两端操作元素
需要双端队列或栈语义时使用 Deque。
// 语义:需要双端队列或栈语义时使用 Deque。
import java.util.ArrayDeque;
import java.util.Deque;
Deque<String> stack = new ArrayDeque<>();
stack.push("page");
// 关键变化:stack.push("page");;调用 push,实参为 "page";将 "page" 压入 stack 栈顶;栈状态更新。
System.out.println(stack.pop());
// 输出:pageMap:键值映射入口
需要按唯一键保存和读取值时使用 Map。
// 语义:需要按唯一键保存和读取值时使用 Map。
import java.util.LinkedHashMap;
import java.util.Map;
public class MapOverviewDemo {
public static void main(String[] args) {
Map<String, Integer> map = new LinkedHashMap<>();
map.put("java", 95);
System.out.println(map.getOrDefault("sql", 0));
// 输出:0
}
}Map 的键唯一,复合更新优先 merge 或 computeIfAbsent;完整 API 见 Map 常用 API。
按语义选择集合实现
先选择接口表达语义,再选择实现;因为声明变量使用 List、Set、Queue、Map 等接口便于替换实现。
// 语义:先选择接口表达语义,再选择实现。
// 具体调用:分别构造空的 ArrayList、HashSet、ArrayDeque 和 HashMap,再读取每个实例的类名。
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
public class CollectionChoiceDemo {
public static void main(String[] args) {
System.out.println(new ArrayList<>().getClass().getSimpleName());
// 输出:ArrayList
System.out.println(new HashSet<>().getClass().getSimpleName());
// 输出:HashSet
System.out.println(new ArrayDeque<>().getClass().getSimpleName());
// 输出:ArrayDeque
System.out.println(new HashMap<>().getClass().getSimpleName());
// 输出:HashMap
}
}使用接口类型声明变量
接口变量隐藏具体实现细节;只有确实需要实现特有 API 时才声明具体类型。
// 语义:接口变量隐藏具体实现细节。
import java.util.ArrayList;
import java.util.List;
public class CollectionInterfaceDemo {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Alice");
System.out.println(names);
// 输出:[Alice]
}
}不常用但需要知道
LinkedList:节点结构不是默认更快
LinkedList 在已知节点位置操作可能方便,但按索引定位仍需遍历,节点对象和缓存局部性也有额外成本;没有明确理由时优先 ArrayList。
import java.util.LinkedList;
import java.util.List;
public class LinkedListBoundaryDemo {
public static void main(String[] args) {
List<String> list = new LinkedList<>(List.of("a", "b"));
list.add("c");
System.out.println(list);
// 输出:[a, b, c]
}
}Arrays.asList:固定大小数组视图
它允许 set 但不能增删;要可变大小列表请复制到 ArrayList。
import java.util.Arrays;
import java.util.List;
public class FixedListBoundaryDemo {
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b");
list.set(0, "A");
// 关键变化:list.set(0, "A");;把 0, "A" 写入 list 的指定位置,返回旧元素;列表状态更新。
System.out.println(list);
// 输出:[A, b]
}
}Collections.synchronizedList:同步包装
同步包装只保证单次操作,遍历与多步组合仍需要外部同步;并发场景应根据读写模式选择专门集合。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SynchronizedListBoundaryDemo {
public static void main(String[] args) {
List<String> list =
Collections.synchronizedList(new ArrayList<>());
// 关键变化:Collections.synchronizedList(new ArrayList<>());;调用 synchronizedList,实参为 new ArrayList<>();返回对底层 List 访问加同步包装的 List。
list.add("java");
synchronized (list) {
System.out.println(list.size());
// 输出:1
}
}
}简单案例
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
public class CollectionsOverviewDemo {
public static void main(String[] args) {
List<String> names = new ArrayList<>(List.of("Alice"));
Queue<String> queue = new ArrayDeque<>();
queue.offer("task");
System.out.println(names + " / " + queue.poll());
// 输出:[Alice] / task
}
}同一个业务可以同时需要 List 的顺序和 Queue 的 FIFO 语义;不要为了少写类型就混用集合。
易混点
- List 允许重复,Set 去重,Queue/Deque 表达进出顺序,Map 通过 key 查 value。
- ArrayList 与 LinkedList 的性能不能只看 Big-O,还要看定位方式、内存和缓存局部性。
- Arrays.asList 是固定大小视图,List.of 是不可变集合,ArrayList 才是常见可变列表。
- 同步包装与不可变集合解决不同问题,任何一个都不会自动形成业务事务。
课后小问
为什么“需要去重”时不应该继续使用 List.contains 循环? 答案:Set 直接表达唯一性,通常能用哈希或排序结构更清晰地处理重复。 解析:List.contains 循环可能是 O(n²),还要手动维护去重逻辑;若需要保序使用 LinkedHashSet。
为什么集合 API 页面要拆成多个细页? 答案:List、Set、Queue、Map 的方法和边界不同,按对象拆页才能通过标题快速定位。 解析:总览页负责选择,细页按常用与不常用分组,并在每个 API 标题下直接给 Java 案例。
本节小结
- 集合选择先看语义,再看顺序、复杂度、内存和并发需求。
- ArrayList、HashSet、ArrayDeque、HashMap 是常见默认实现。
- 具体 API 和边界已分别拆到 List、Set、Queue、Map 与排序页面。
- 同步包装、固定大小视图和不可变集合不能混为一谈。
快速回顾
- 能按索引、唯一性、进出顺序和键值查找选择接口。
- 能区分四类集合的核心语义。
- 能解释 ArrayList、LinkedList、HashSet、ArrayDeque 的边界。
- 能从总览跳到对应 API 细页。
阿源的知识库