集合框架是 Java 开发中使用频率最高的 API 体系之一,也是后端面试的核心必考点。从日常的数据存储到高并发场景下的缓存、任务队列,集合的选型直接决定了代码的性能、稳定性与可维护性。
很多开发者对集合的认知停留在「会用」层面,对底层实现、并发特性、边界坑点一知半解:面试时答不深原理,生产中频繁踩坑。本文系统梳理 Java 集合框架完整知识体系,涵盖核心接口、主流实现类底层原理、迭代机制、高频坑点、Stream API 以及生产场景选型指南,一篇帮你建立完整的集合知识体系。
一、集合框架整体体系
1.1 顶层接口划分
Java 集合框架主要分为两大分支,顶层接口职责清晰:
- Iterable:所有集合的根接口,提供迭代器能力,支持增强 for 循环遍历。
- Collection:单值集合的顶层接口,下分三大子体系:List(有序可重复)、Set(无序去重)、Queue(队列结构)。
- Map:键值对集合的顶层接口,独立于 Collection 体系,以 key-value 形式存储数据。
1.2 主流实现类核心特性速查表
这张表可以快速定位各个集合的基础属性,面试和选型时可以快速对照:
| 类型 | 接口 | 常用实现 | 底层结构 | 是否有序 | null 规则 | 线程安全 |
|---|---|---|---|---|---|---|
| List | List | ArrayList | Object[] 数组 | 按索引有序 | 允许所有元素为 null | 否 |
| List | List | LinkedList | 双向链表 | 按索引有序 | 允许所有元素为 null | 否 |
| List | List | Vector | Object[] 数组 | 按索引有序 | 允许所有元素为 null | 是,全方法加 synchronized |
| List | List | CopyOnWriteArrayList | 数组 + 写时复制机制 | 按索引有序 | 允许所有元素为 null | 是,写加锁,读无锁 |
| Set | Set | HashSet | 基于 HashMap | 无序 | 允许一个 null 元素 | 否 |
| Set | Set | LinkedHashSet | 基于 LinkedHashMap | 插入有序 | 允许一个 null 元素 | 否 |
| Set | Set | TreeSet | 基于 TreeMap(红黑树) | 自然/自定义排序有序 | 默认不允许 null(自定义比较器可支持) | 否 |
| Set | Set | EnumSet | 位向量 | 枚举自然顺序 | 不允许 null | 否 |
| Map | Map | HashMap | 数组 + 链表/红黑树 | 无序 | key 允许一个 null,value 允许多个 null | 否 |
| Map | Map | LinkedHashMap | HashMap + 双向链表 | 插入/访问有序 | key 允许一个 null,value 允许多个 null | 否 |
| Map | Map | TreeMap | 红黑树 | key 自然/自定义排序 | key 默认不允许 null | 否 |
| Map | Map | Hashtable | 数组 + 链表 | 无序 | key/value 均不允许 null | 是,全方法加 synchronized |
| Map | Map | ConcurrentHashMap | 数组 + 链表/红黑树 | 无序 | key/value 均不允许 null | 是,CAS + 桶级锁 |
| Map | Map | ConcurrentSkipListMap | 跳表 | key 有序 | key/value 均不允许 null | 是,无锁算法 |
| Queue | Queue | ArrayDeque | 循环数组 | FIFO 队列顺序 | 不允许 null | 否 |
| Queue | Queue | PriorityQueue | 二叉堆 | 优先级顺序 | 不允许 null | 否 |
| BlockingQueue | BlockingQueue | ArrayBlockingQueue | 数组 | FIFO 队列顺序 | 不允许 null | 是,全局锁 |
| BlockingQueue | BlockingQueue | LinkedBlockingQueue | 链表 | FIFO 队列顺序 | 不允许 null | 是,读写分离锁 |
二、List 集合深度解析
2.1 ArrayList
ArrayList 是日常开发最常用的 List 实现,底层基于动态数组实现,随机访问性能优异。
核心特性
- 懒初始化(JDK8+):无参构造不会立即创建容量为 10 的数组,首次调用 add 方法时才会初始化默认容量。
- 扩容规则:默认扩容为原容量的 1.5 倍,计算公式为
oldCapacity + (oldCapacity >> 1),扩容时会复制整个底层数组。 - 时间复杂度:
- 随机下标访问
get(int index):O(1),直接通过数组下标定位 - 尾部添加
add(E e):均摊 O(1),仅扩容时触发数组复制 - 中间插入/删除:O(n),需要移动后续所有元素
- 随机下标访问
- 实现了
RandomAccess标记接口,遍历时优先使用普通 for 循环,性能优于迭代器。 - 线程不安全,多线程并发修改需手动加锁或使用并发集合。
- 性能优化:批量插入大量元素前,可调用
ensureCapacity(int minCapacity)预分配容量,减少多次扩容拷贝。 - 序列化特性:底层数组
elementData被transient修饰,通过writeObject/readObject自定义序列化,只序列化真实存在的元素,不序列化空槽位,节省空间。 - 遍历删除坑点:正向 for 循环遍历调用
remove会出现元素漏检问题,推荐使用迭代器Iterator.remove()或反向遍历删除。 - 可通过
trimToSize()方法将底层数组容量压缩至当前元素个数,适合长期存活的集合节省内存。
subList 高频坑点
subList(int fromIndex, int toIndex)返回的是原列表的视图,而非独立副本,这是非常容易踩的坑:
- 对子列表的非结构性修改会同步反映到原列表,反之亦然。
- 对原列表进行结构性修改(add/remove)后,再访问子列表会抛出
ConcurrentModificationException。 - 子列表是 ArrayList 内部类
SubList的实例,自身未实现Serializable接口,直接序列化会抛出NotSerializableException;原 ArrayList 本身支持序列化。
List<String> list = new ArrayList<>(List.of("a", "b", "c")); List<String> sub = list.subList(0, 2); sub.set(0, "x"); // 修改会同步到原列表 System.out.println(list); // 输出 [x, b, c] list.add("d"); // 原列表结构性修改 // sub.get(0); // 抛出 ConcurrentModificationException2.2 LinkedList
底层基于双向链表实现,每个节点包含prev、item、next三个属性。
核心特性
- 同时实现
List和Deque接口,可作为列表、双端队列、栈使用。 - 时间复杂度:
- 头尾节点增删:O(1),直接修改指针指向
- 按索引访问
get(int index):O(n),源码会先判断 index 靠近头还是尾,选择从头部或尾部就近遍历,最坏时间复杂度仍为 O(n) - 按对象查找删除:O(n),需先遍历定位节点
- 通过
ListIterator或已知节点引用进行增删操作,时间复杂度为 O(1)。 - 随机访问性能远低于 ArrayList,且每个节点需要额外存储指针,内存占用更大。
- 适用于频繁头尾增删、双端队列的场景,随机访问多的场景优先选 ArrayList。
2.3 Vector / Stack
- Vector 底层同样基于数组实现,所有方法都加了
synchronized关键字,线程安全但性能很差,属于过时类。 - Vector 默认扩容为原容量的 2 倍;若指定了
capacityIncrement,则扩容为旧容量 + 增量值。 - Stack 继承自 Vector,是栈的古老实现,同样过时。
- 栈场景推荐使用
ArrayDeque替代,性能更好且接口更规范:
Deque<String> stack = new ArrayDeque<>(); stack.push("a"); stack.push("b"); String top = stack.pop(); // 返回 "b"2.4 CopyOnWriteArrayList
基于**写时复制(Copy-On-Write)**机制实现的并发 List,是读多写少场景的首选。
核心原理
- 写操作(add/set/remove)使用
ReentrantLock加锁,复制整个底层数组,修改完成后替换原数组引用。 - 读操作完全无锁,直接读取当前数组引用,读性能极高。
- 迭代器基于创建时的数组快照,具有快照一致性,迭代期间完全看不到后续的新增、删除修改,不会抛出
ConcurrentModificationException。 - 迭代器不支持 add/remove/set 等修改操作,调用会直接抛出
UnsupportedOperationException。 - 写操作开销极大(每次复制整个数组),绝对不适合高频写场景。
- 适合读多写极少的并发场景,比如配置列表、白名单等更新频率极低的数据。
三、Set 集合深度解析
3.1 HashSet
最常用的去重集合,底层完全基于 HashMap 实现。
核心特性
- 元素作为 HashMap 的 key 存储,value 为固定的 Object 常量
PRESENT。 - 元素无序,允许存入一个 null 元素。
- 去重依赖
hashCode()和equals()方法:- 先根据 hashCode 定位哈希桶位置
- 桶内已有元素时,通过 equals 方法判断是否相等
- 自定义对象作为元素时,重写 equals 方法必须同时重写 hashCode,否则会出现去重失效、查询异常等问题。
3.2 LinkedHashSet
HashSet 的子类,内部使用 LinkedHashMap 维护元素的插入顺序。
- 迭代顺序与元素插入顺序完全一致。
- 仅维护插入顺序,修改已有元素不会改变其在链表中的位置。
- 性能略低于 HashSet,因为需要额外维护双向链表。
- 允许一个 null 元素。
3.3 TreeSet
底层基于 TreeMap 红黑树实现,支持元素排序。
- 支持自然排序(元素实现 Comparable 接口)或自定义 Comparator 比较器。
- null 规则:默认自然排序下不允许 null 元素,插入会抛出
NullPointerException;若传入支持 null 比较的自定义 Comparator(如Comparator.nullsFirst(String::compareTo)),则可以正常存入 null 元素。 - 去重不依赖 equals,而是依赖
compareTo或compare方法返回 0。 - 实现了
NavigableSet接口,提供了比SortedSet更丰富的近邻查询、范围截取能力。 - 支持丰富的范围查询:
first()、last()、subSet()、headSet()、tailSet()。
3.4 EnumSet
专为枚举类型设计的 Set 实现,是性能最高的 Set 实现类。
- 内部使用位向量实现,内存占用极小,操作效率极高。
- 根据枚举元素数量分为两个实现:
- 元素 ≤ 64:
RegularEnumSet,单个 long 型位掩码 - 元素 > 64:
JumboEnumSet,long 数组实现
- 元素 ≤ 64:
- 不允许 null 元素,所有元素必须是指定枚举类型。
- 推荐通过工厂方法创建:
noneOf()、allOf()、of()。
enum Color { RED, GREEN, BLUE } Set<Color> colors = EnumSet.of(Color.RED, Color.GREEN);四、Map 集合深度解析
Map 是集合框架的重中之重,尤其是 HashMap 和 ConcurrentHashMap,几乎是面试必考题。
4.1 HashMap
底层结构演进
- JDK7:数组 + 链表,采用头插法插入元素,并发扩容可能产生环形链表导致死循环。
- JDK8:数组 + 链表/红黑树,采用尾插法,引入红黑树优化哈希冲突严重时的查询性能。
- 通过扰动函数优化 hash 分布:
h = key.hashCode() ^ (h >>> 16),将哈希值高 16 位混合到低 16 位,减少高位差异带来的哈希冲突。 - 容量强制为 2 的幂,通过
hash & (n - 1)快速定位哈希桶,比取模效率更高。 - 默认初始容量 16,负载因子 0.75,扩容阈值 = 容量 × 负载因子。
树化与退化规则
这是面试高频考点,完整触发条件如下:
- 树化条件:链表长度 ≥ 8 且数组容量 ≥ 64 时,链表转化为红黑树;如果容量小于 64,优先扩容而非树化。
- 退化条件:
- 扩容重哈希时,若红黑树中元素个数 ≤ 6,会退化为链表;
- 普通
remove操作删除节点后,若树结构过小(如根节点为空、左右子树缺失),也会立即触发退化,无需等待扩容。
- 树化阈值设为 8 的统计学依据:负载因子 0.75 下,链表长度达到 8 的概率符合泊松分布,约为千万分之六,概率极低。
put 执行流程
- 判断 table 数组是否为空,为空则初始化数组。
- 计算 key 的 hash 值,通过
hash & (n - 1)定位哈希桶。 - 桶为空,直接新建节点插入。
- 桶为红黑树节点,执行红黑树插入逻辑。
- 桶为链表,遍历链表:
- 找到相同 key,替换对应的 value。
- 未找到相同 key,尾插新节点;插入后链表长度达到 8,检查是否需要树化。
- 插入完成后,若 size 超过扩容阈值,触发扩容。
扩容机制
- 扩容为原容量的 2 倍,保证容量始终是 2 的幂。
- JDK8 优化了元素重定位逻辑:判断
hash & oldCap是否为 0,为 0 则留在原位,否则移动到「原位 + 旧容量」的位置,无需重新计算 hash。
并发安全性说明
- JDK7:头插法 + 扩容迁移,多线程同时扩容可能形成环形链表,导致 get 操作死循环。
- JDK8:尾插法修复了环形链表问题,但并发场景下仍可能出现数据覆盖、元素丢失等问题,依旧线程不安全,不能替代 ConcurrentHashMap。
其他特性
- 允许一个 null key,多个 null value。
- 自定义对象作为 key 时,必须同时重写
equals()和hashCode(),否则会出现去重失效、get 查询返回 null 等问题。 - JDK8+ 支持
getOrDefault、putIfAbsent、compute、merge等默认方法(非原子操作)。
4.2 LinkedHashMap
HashMap 的子类,通过额外的双向链表维护元素顺序。
accessOrder = false(默认):维护元素插入顺序。accessOrder = true:维护访问顺序,get/put 操作后元素会移到链表尾部。- 可通过重写
removeEldestEntry方法实现 LRU 缓存:
LinkedHashMap<String, Integer> lruCache = new LinkedHashMap<>(16, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry<String, Integer> eldest) { return size() > 3; // 插入第4个元素时,淘汰最久未访问的头部元素 } };说明:
removeEldestEntry在每次插入新节点完成后调用,返回 true 时会移除链表头部的最久未访问元素,而非插入前拒绝新元素。
4.3 TreeMap
基于红黑树实现的有序 Map。
- key 支持自然排序或自定义 Comparator。
- 默认自然排序下不允许 null key;自定义比较器支持 null 时可存入 null key;value 允许为 null。
- 实现了
NavigableMap接口,提供了比SortedMap更丰富的近邻查询和范围截取能力。 - 支持丰富的范围查询:
firstKey()、lastKey()、subMap()、headMap()、tailMap()、ceilingKey()、floorKey()。 - 去重依据
compareTo/compare方法返回 0。
4.4 Hashtable
- 所有方法都加了 synchronized,包括读操作,性能极低,属于过时类。
- 不允许 null key 和 null value。
- 默认初始容量 11,扩容公式:
旧容量 × 2 + 1。 Properties是其子类,用于读取配置文件。- 并发场景优先使用 ConcurrentHashMap,不要用 Hashtable。
4.5 ConcurrentHashMap
并发场景下的首选 Map,面试绝对高频考点。
版本演进
- JDK7:采用分段锁 Segment 机制,每段维护独立的 HashEntry 数组,默认 16 个段,锁粒度较粗。
- JDK8:重构为CAS + synchronized 锁桶头节点,数据结构与 HashMap 一致,锁粒度细化到单个哈希桶,并发性能大幅提升。
核心并发控制
- 初始化:通过
sizeCtl变量 CAS 控制,避免并发初始化。 - 读操作基本无锁,依赖 volatile 保证可见性(树节点遍历可能加锁,普通读无锁)。
- 写操作仅锁定冲突桶的头节点,不同桶之间互不阻塞。
- 扩容支持多线程协助迁移,已迁移的桶用 ForwardingNode 标记。
sizeCtl 完整语义
0:数组尚未初始化(table 为 null)。- 正数:扩容阈值(容量 × 负载因子)。
-1:表示正在初始化。- 扩容期间:整体为负数,高 16 位存储扩容戳(resizeStamp),低 16 位存储「参与扩容的线程数 + RESIZE_STAMP_OFFSET」,不能直接等价于线程数。
size 计算原理
采用baseCount + CounterCell 数组的分布式计数方案:
- 无竞争时直接累加 baseCount。
- 有竞争时通过 CAS 操作 CounterCell 数组,避免全局锁。
- 高并发下
size()返回的是近似值而非精确值。 - 推荐使用
mappingCount()替代size(),前者返回 long 类型,可避免 int 溢出问题。
核心特性
- 不允许 null key 和 null value:避免 get 返回 null 时无法区分「key 不存在」和「值为 null」的二义性。
- 提供丰富的原子操作:
putIfAbsent、replace、compute、merge。 compute、merge是原子操作,但如果传入的计算逻辑过重,会阻塞其他线程访问同一个哈希桶,生产环境应避免在其中执行耗时操作。- 迭代器具有弱一致性,不抛出
ConcurrentModificationException。
4.6 其他特殊 Map
- ConcurrentSkipListMap:基于跳表实现的并发有序 Map,无锁算法,支持高并发有序访问和范围查询。
- WeakHashMap:key 为弱引用,一旦 key 没有外部强引用,下次 GC 时对应 Entry 会被自动清理;value 为强引用,若 value 直接或间接持有对应 key 的强引用,会导致 key 无法被回收,引发内存泄漏。仅适合特定弱引用缓存场景,不推荐作为通用缓存使用。
- IdentityHashMap:使用
==比较 key 引用地址,而非equals方法;底层采用线性探测法而非拉链法;允许 key 为 null,允许「重复」key(只要对象引用不同)。 - EnumMap:key 必须为枚举类型,内部数组实现,性能极高,不允许 null key。
五、迭代机制与并发特性
5.1 fail-fast(快速失败)
- 原理:迭代时检测
modCount变量变化,若集合发生结构性修改,立即抛出ConcurrentModificationException。 - 代表:ArrayList、HashMap、HashSet 等普通非并发集合。
- 特性说明:
- 单线程下结构性修改迭代 100% 触发异常;
- 多线程场景下因 modCount 没有 volatile 修饰,无法保证可见性,可能出现漏检测不抛异常的情况,属于尽力而为的快速失败机制,不能替代线程安全控制。
- fail-fast 不是线程安全保证,只是快速暴露问题的机制。
List<String> list = new ArrayList<>(List.of("a", "b", "c")); for (String s : list) { if ("b".equals(s)) { list.remove(s); // 抛出 ConcurrentModificationException } }5.2 fail-safe(安全失败)
迭代过程不会抛出ConcurrentModificationException,分为两类:
- 快照式:CopyOnWriteArrayList、CopyOnWriteArraySet,迭代器持有创建时的数组快照,与后续修改完全隔离,具备快照一致性。
- 弱一致性:ConcurrentHashMap、ConcurrentSkipListMap 等并发集合,不检测修改,迭代过程可能反映部分最新修改,不保证实时一致。
5.3 Collections.synchronizedXXX 迭代坑点
Collections.synchronizedList/Set/Map返回的同步包装类,迭代器不具备线程安全性,迭代时必须手动对集合对象加锁,否则并发修改仍会触发ConcurrentModificationException:
List<String> syncList = Collections.synchronizedList(new ArrayList<>()); synchronized (syncList) { // 必须手动加锁,锁对象为返回的包装集合本身 for (String s : syncList) { // 业务逻辑 } }六、队列体系详解
6.1 Queue 基础接口
先进先出(FIFO)队列,提供两类操作方法:
| 操作 | 失败抛异常 | 失败返回特殊值 |
|---|---|---|
| 插入元素 | add(e) | offer(e) |
| 移除并返回头部 | remove() | poll() |
| 查看头部元素 | element() | peek() |
6.2 Deque 双端队列
两端均可入队/出队,可作为栈使用。
- 推荐实现
ArrayDeque:底层为循环数组,默认容量 16,扩容为原容量的 2 倍,容量始终保持为 2 的幂。 - 作为栈性能优于 Stack,作为队列性能优于 LinkedList(连续内存,CPU 缓存命中率更高)。
- 不允许 null 元素。
6.3 BlockingQueue 阻塞队列
阻塞队列是生产者-消费者模型的核心组件,广泛用于异步任务、流量削峰等场景。
常用实现对比
| 实现类 | 底层结构 | 有界性 | 特点 |
|---|---|---|---|
| ArrayBlockingQueue | 数组 | 有界 | 全局单锁 + 两个 Condition,支持公平/非公平模式,读写互斥 |
| LinkedBlockingQueue | 链表 | 默认无界 | 双锁(put 锁 + take 锁),读写可并行,高并发性能更优;默认容量 Integer.MAX_VALUE,生产环境严禁使用无参构造,必须手动指定容量,避免 OOM 风险 |
| PriorityBlockingQueue | 二叉堆 | 无界 | 按优先级出队,元素需实现 Comparable 或传入 Comparator |
| DelayQueue | 优先级队列 | 无界 | 元素需实现 Delayed 接口,到期后才能出队;getDelay实现不当可能导致元素永远无法出队 |
| SynchronousQueue | 无存储 | 无容量 | 插入必须等待消费,用于线程间直接传递任务 |
四类操作方法
| 操作 | 抛异常 | 返回特殊值 | 阻塞 | 超时等待 |
|---|---|---|---|---|
| 插入元素 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
| 移除并返回头部 | remove() | poll() | take() | poll(time, unit) |
| 查看头部 | element() | peek() | 无 | 无 |
生产者-消费者示例
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10); // 生产者 new Thread(() -> { try { queue.put("task"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start(); // 消费者 new Thread(() -> { try { String task = queue.take(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start();6.4 PriorityQueue 注意事项
- 底层为最小堆,仅保证堆顶元素最小,不保证整个队列整体有序。
- 元素必须实现
Comparable接口,或构造时传入自定义Comparator,否则插入时会抛出ClassCastException。 - 不允许 null 元素。
七、工具类与高频坑点避坑
7.1 Collections 工具类
- 通用操作:
sort、binarySearch、reverse、shuffle、fill、copy、max、min、frequency、disjoint、swap。 unmodifiableXXX:返回不可修改视图,原集合修改会反映到视图。synchronizedXXX:返回同步包装类,全方法加锁,迭代需手动加锁。emptyXXX/singletonXXX:返回空集合或单元素集合,均不可变。
7.2 Arrays.asList() 经典坑点
这个方法几乎每个 Java 开发者都踩过坑:
- 返回内部类
Arrays$ArrayList,不是java.util.ArrayList。 - 不支持 add/remove 等结构性修改,抛出
UnsupportedOperationException。 - 支持 set 修改元素,与原数组共享底层数组,修改互相影响。
- 传入基本类型数组时,会被当作单个元素(如
int[]变成List<int[]>)。
正确的基本类型数组转集合写法:
int[] nums = {1, 2, 3}; // 错误:List<int[]> numList = Arrays.asList(nums); List<Integer> numList = Arrays.stream(nums).boxed().collect(Collectors.toList());7.3 JDK9+ 不可变集合
List.of()/Set.of()/Map.of()是真正的不可变集合。- 元素不能为 null,修改抛出
UnsupportedOperationException。 Set.of()不允许重复元素;Map.of()不允许重复 key,最多支持 5 个键值对(10 个参数),超过请使用Map.ofEntries()。
7.4 Comparator 与 Comparable
Comparable:内部比较接口,实现compareTo方法,定义自然排序。Comparator:外部比较器,实现compare方法,策略模式,可灵活切换排序规则。- 常用工具方法:
comparing()、thenComparing()、reversed()、nullsFirst()、nullsLast()。
7.5 Map 遍历方式对比
keySet():遍历 key,获取 value 需要二次查找,性能较差。entrySet():遍历键值对,推荐使用,避免二次查找。values():遍历 value,无法获取对应 key。- 迭代器遍历可安全删除元素。
八、Stream API 核心要点
8.1 操作分类
- 中间操作(惰性):只记录操作,不触发计算,流可继续链式调用。
- 无状态:
map、filter、peek等,元素处理独立,无需缓存 - 有状态:
sorted、distinct、limit、skip等,需要缓存历史元素,性能开销更大
- 无状态:
- 终端操作(触发计算):流被消费,不可复用,如
forEach、collect、count、reduce、findFirst等。 - 短路操作:无需遍历全部元素即可终止,如
limit、findFirst、anyMatch、allMatch。
注:有状态和短路操作并不互斥,例如
limit既属于有状态中间操作,也属于短路操作。
8.2 Collectors 常用收集器
toList()/toSet():收集为 List / Set。toMap():- key 重复默认抛出
IllegalStateException,可指定 mergeFunction 处理重复 key。 - 无论是否指定合并函数,value 为 null 都会抛出
NullPointerException(底层调用 Map.merge 方法),这是高频生产 Bug。
- key 重复默认抛出
groupingBy():分组收集,支持嵌套下游收集器。partitioningBy():按布尔值二分分组。joining():字符串拼接,支持分隔符、前后缀。
8.3 注意事项
- 流只能消费一次,重复使用抛出
IllegalStateException。 - peek 陷阱:
peek是中间操作,若没有终端操作触发计算,peek 中的逻辑不会执行,常出现 debug 打印无输出的问题。 - 并行流
parallelStream()默认使用公共 ForkJoinPool,默认并行度为 CPU 核心数 - 1。 - 并行流不要操作非线程安全的外部集合,否则会出现并发修改异常或数据错乱;并行流不适合 I/O 阻塞型任务,I/O 密集场景应自定义线程池,避免阻塞公共池影响其他程序。
stream.collect(Collectors.toList()):JDK8-15 返回 ArrayList,允许 null 元素;JDK16 后底层实现变更,但仍允许 null 元素。- JDK16+ 新增
Stream.toList()实例方法,返回不可变 List,允许包含 null 元素;若需要禁止 null 的不可变集合,应使用Collectors.toUnmodifiableList()。
九、生产场景选型指南
List 选型
| 场景 | 选择 |
|---|---|
| 频繁随机访问 | ArrayList |
| 频繁头尾增删 | LinkedList 或 ArrayDeque |
| 读多写极少的并发场景 | CopyOnWriteArrayList |
| 需要线程安全且写操作较多 | 不推荐 Collections.synchronizedList(全局锁性能差),可考虑读写锁封装 ArrayList 或使用并发队列 |
Set 选型
| 场景 | 选择 |
|---|---|
| 普通去重 | HashSet |
| 保持插入顺序 | LinkedHashSet |
| 需要排序 | TreeSet |
| 并发读多写极少 | CopyOnWriteArraySet 或 ConcurrentHashMap.newKeySet() |
| 枚举元素 | EnumSet |
Map 选型
| 场景 | 选择 |
|---|---|
| 普通键值对存储 | HashMap |
| 保持插入/访问顺序 | LinkedHashMap |
| 需要 key 排序 | TreeMap |
| 高并发场景 | ConcurrentHashMap |
| 高并发且需要有序 | ConcurrentSkipListMap |
| 枚举 key | EnumMap |
Queue 选型
| 场景 | 选择 |
|---|---|
| 普通 FIFO 队列 | ArrayDeque |
| 双端队列 / 栈 | ArrayDeque |
| 优先级队列 | PriorityQueue |
| 阻塞生产者-消费者模型 | ArrayBlockingQueue / LinkedBlockingQueue |
| 延迟任务调度 | DelayQueue |
| 线程间直接传递任务 | SynchronousQueue |
| 高并发非阻塞队列 | ConcurrentLinkedQueue |
写在最后
Java 集合框架不是只有 ArrayList 和 HashMap,不同场景下选择合适的实现类,能大幅提升代码性能和稳定性。普通业务场景优先使用主流实现,并发场景优先用 JUC 包下的并发集合,尽量避免 Vector、Hashtable 等老旧过时类。
本文覆盖了集合框架 90% 以上的核心考点和生产常用知识点,修正了大量网络常见误区,补充了源码级细节和生产踩坑点,建议收藏备用。如果有补充或者疑问,欢迎在评论区留言交流~
推荐标签:#Java #集合框架 #HashMap #并发编程 #Java面试 #后端开发