title: 2.集合框架
1.集合框架概述与基本使用
学习并理解 Java 集合框架的整体结构及其各个常见集合类。
2.底层实现及原理
深入研究集合框架背后的底层实现。
3.线程安全与并发场景
了解在多线程环境中,如何保证集合的线程安全。
4.设计模式与源码设计思想
探讨集合框架中的设计模式(如工厂模式、单例模式等)及其在源码中的应用。
Java 的集合框架(Collection Framework)是一个强大且统一的 API,用于存储和操作对象数据。它大致可以分为两大体系:
Collection 与 Map 的两大体系
Collection
接口是集合层次结构的根,用于表示一组对象(元素),其子接口包括:
List
:有序集合,允许重复元素。Set
:不允许重复元素,无序或有序视实现而定。Queue
:支持队列操作的集合,通常用于按一定顺序处理元素(如 FIFO)。Map
接口独立于 Collection
,用于存储**键值对(key-value)**映射关系:
HashMap
、TreeMap
、LinkedHashMap
等。Collection 下的 List、Set、Queue 简介
接口 | 特点 | 常见实现类 |
---|---|---|
List |
有序,可重复 | ArrayList , LinkedList , Vector |
Set |
无序,不可重复 | HashSet , LinkedHashSet , TreeSet |
Queue |
先进先出(FIFO) | LinkedList , PriorityQueue , ArrayDeque |
• Map 接口的独立地位
Map
不继承 Collection
,因为其语义(键值映射)与传统集合不同。put
)、获取 (get
)、删除 (remove
)、遍历等。接口 vs 实现类的关系图解
Iterable
|
Collection Map
/ | \ |
List Set Queue SortedMap
| | | |
ArrayList HashSet LinkedList TreeMap
核心接口与通用方法Java 集合框架中的所有类都遵循统一的接口规范,使得学习与使用变得更为一致和高效。
• add()、remove()、contains() 等基础方法
add(E e)
:向集合中添加元素。remove(Object o)
:移除指定对象(第一次出现)。contains(Object o)
:判断集合中是否包含该对象。isEmpty()
:判断集合是否为空。size()
:获取集合大小。注意: 不同集合对
add()
的行为不同,如Set
添加重复元素会失败。
equals()、hashCode() 在集合中的作用
Set
、Map
等集合依赖 equals()
和 hashCode()
判断对象是否“相等”。@Override
public boolean equals(Object obj) {
// 比较字段值
}
@Override
public int hashCode() {
// 返回基于字段的 hash
}
泛型支持与类型安全
Java 5 引入泛型后,集合类支持类型参数:
List<String> list = new ArrayList<>();
避免类型转换错误(ClassCastException
):
String s = list.get(0); // 不再需要强制转换
fail-fast 与 fail-safe 行为差异
ConcurrentModificationException
。ArrayList
, HashMap
, HashSet
CopyOnWriteArrayList
, ConcurrentHashMap
集合遍历是集合使用中最常见的操作之一,Java 提供了多种方式。
Iterator / enhanced for / forEach
Iterator
是最通用的遍历方式:
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
}
for-each
语法糖:
for (String s : list) {
// 自动调用 Iterator
}
Collection.forEach(Consumer)
(Java 8+):
list.forEach(s -> System.out.println(s));
ListIterator 的前后遍历
仅 List
接口支持 ListIterator
,可双向遍历:
ListIterator<String> it = list.listIterator();
while (it.hasNext()) {
String s = it.next();
}
while (it.hasPrevious()) {
String s = it.previous();
}
可通过 add()
、remove()
等在遍历中安全修改集合。
lambda 与 stream 的遍历新姿势(Java 8+)
Java 8 引入 Stream API,使集合操作更函数式和声明式:
list.stream()
.filter(s -> s.startsWith("A"))
.map(String::toUpperCase)
.forEach(System.out::println);
并行流(parallelStream)可加速处理大数据量(注意线程安全)。
List 是最常用的集合类型,特点是:有序、可重复。
ArrayList
(顺序数组)
O(1)
)ensureCapacity()
可提前扩容避免重复复制Collections.synchronizedList
实现线程安全LinkedList
(链表实现)
O(n)
)Deque
使用(支持栈/队列)Vector
(线程安全的 ArrayList)
ArrayList
类似,但所有方法都被 synchronized
修饰Collections.synchronizedList(new ArrayList<>())
Set 体现的是数学集合语义:无序、不可重复。
HashSet
(最常用)
HashMap
实现,元素作为键,值为常量 PRESENT
hashCode()
与 equals()
保证唯一性O(1)
(理想情况)equals/hashCode
导致“重复元素”失效LinkedHashSet
HashSet
+ 双向链表** TreeSet
**
TreeMap
支撑)Comparable
或使用自定义 Comparator
O(log n)
Map 是 Java 中用于处理键值对映射的核心接口。
HashMap
(最常用)
进阶:
hashCode()
与 equals()
LinkedHashMap
HashMap
,同时维护插入顺序的双向链表accessOrder=true
,并重写 removeEldestEntry()
TreeMap
基于红黑树,键需排序(Comparable
或 Comparator
)
适合范围查找、自动排序,如:
map.subMap(10, 20); // 获取 key 在 10~20 之间的子映射
Hashtable
(线程安全但已淘汰)
synchronized
,效率低ConcurrentHashMap
Queue 表现先进先出(FIFO)行为,广泛用于任务调度、缓存等。
LinkedList
实现 Queue
Queue
和 Deque
PriorityQueue
Comparable
,也可传入 Comparator
ArrayDeque
Stack
和 LinkedList
O(1)
LinkedList
更轻量级,无链表指针开销接口 | 实现类 | 底层结构 | 有序性 | 线程安全 | 特点 |
---|---|---|---|---|---|
List | ArrayList | 数组 | 有序 | × | 快速随机访问 |
List | LinkedList | 双向链表 | 有序 | × | 快速增删 |
Set | HashSet | Hash 表 | 无序 | × | 快速查重 |
Set | TreeSet | 红黑树 | 排序 | × | 自动排序 |
Map | HashMap | Hash 表 | 无序 | × | 快速 key-value 映射 |
Map | TreeMap | 红黑树 | 排序 | × | 按 key 排序 |
Queue | PriorityQueue | 堆 | 排序 | × | 优先处理 |
Queue | ArrayDeque | 循环数组 | 有序 | × | 双端操作 |
因为Java集合并没有太多理解困难的抽象知识,更多是在使用中的选择和应用,对于实现原理和源码分析已经有很多成熟的文章和视频,所以我不再重复赘述。
Java集合常见面试题总结(上) | JavaGuide
CarpenterLee/JCFInternals: 深入理解Java集合框架
google/guava: Google core libraries for Java
在多线程并发环境中,如果使用 Java 中默认的集合类(如 ArrayList
、HashMap
、LinkedList
等),很容易出现数据不一致、程序异常甚至死循环等问题。这是因为这些集合类本身没有线程安全保障,多个线程同时读写时会引发竞态条件。
示例一:ArrayList 的并发问题
java复制编辑List list = new ArrayList<>();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
list.add(i);
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Size: " + list.size());
预期结果:2000
实际可能结果:小于 2000,甚至抛出 ConcurrentModificationException
或 ArrayIndexOutOfBoundsException
。
原因:多个线程同时调用 add()
,在扩容或修改内部数组时发生冲突,导致数据错乱或覆盖。
示例二:HashMap 的并发死循环(JDK 1.7)
java复制编辑Map map = new HashMap<>();
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
map.put(i, i);
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
在 JDK 1.7 中,如果在多线程环境下频繁 put
,可能会触发 rehash 时链表形成循环,导致 CPU 100% 卡死在遍历中(虽然 JDK 1.8 改进为红黑树后几率降低,但仍不安全)。
因此,在并发环境中使用这些集合类需要格外小心,不能直接使用,应使用线程安全版本或并发集合类替代。
在多线程环境中,普通集合类(如 ArrayList
、HashMap
)由于缺乏内部同步机制,无法保障并发操作下的数据一致性,容易引发线程安全问题。为了解决这一问题,Java 提供了多种线程安全的集合实现方案,分别适用于不同的使用场景。
1. 使用 Collections.synchronizedXXX 包装
Java 的 Collections
工具类提供了若干静态方法,可以将原本非线程安全的集合包装成线程安全的版本,例如:
java复制编辑List syncList = Collections.synchronizedList(new ArrayList<>());
Map syncMap = Collections.synchronizedMap(new HashMap<>());
这些包装方法的底层实现通过 synchronized
关键字为集合的每个操作加锁,从而保证线程安全。但需要注意:
这种方式简单直接,适合轻量级的并发需求场景,但并不推荐用于复杂或高频操作的系统中。
2. Concurrent 包下的高性能集合类
为了提供更优的并发性能,Java 5 引入了 java.util.concurrent
包,内置了多种基于分段锁或无锁机制的线程安全集合,它们在企业开发中广泛应用:
ConcurrentHashMap
HashMap
的线程安全替代品,JDK 7 使用分段锁(Segment),JDK 8 开始改为使用 CAS + synchronized 实现,具有更高的并发性能。适合高读写并发场景。CopyOnWriteArrayList
ConcurrentLinkedQueue
ConcurrentSkipListMap
/ ConcurrentSkipListSet
这些集合的设计核心是降低锁粒度,避免全局锁,通过细粒度锁或乐观并发控制机制(如 CAS)来提升并发能力。
3. 并发集合的使用建议
CopyOnWriteArrayList
。ConcurrentHashMap
。ConcurrentLinkedQueue
。ConcurrentSkipListMap
或 ConcurrentSkipListSet
。为了提升集合在并发场景下的性能和可扩展性,Java 的 java.util.concurrent
包引入了多种并发集合,其设计核心在于降低锁粒度、分离读写操作和使用无锁算法,相比传统的同步集合性能更优。
以下是几种常见并发集合的底层原理简介:
1. ConcurrentHashMap
ConcurrentHashMap
是最常用的线程安全 Map
实现:
synchronized
锁定单个桶,读操作通过 volatile
保证可见性,避免加锁。优点: 高性能、高可扩展性、适合读写并发场景。
注意: 并不支持 null 键或 null 值。
2. CopyOnWriteArrayList
适合读多写少的场景,其核心思想是写时复制:
add()
、remove()
)都会复制一份新数组。ReentrantLock
保证串行化。优点: 读操作无锁,读性能极高。
缺点: 写操作开销大,不适合频繁修改的场景。
3. ConcurrentLinkedQueue
是一个基于 非阻塞链表 的并发队列,实现了 无锁队列算法:
优点: 高吞吐,适合生产者-消费者模型、任务调度等场景。
缺点: 在极高并发下可能存在 ABA 问题(JDK 已优化)。
4. ConcurrentSkipListMap / Set
使用跳表(Skip List)实现的有序并发集合:
ReentrantLock
做局部加锁。优点: 支持范围查询、有序遍历,是线程安全的 TreeMap 替代品。
Java 并发集合的底层实现都体现了对传统加锁方式的优化,通过合理的算法设计和内存模型控制,使得集合在多线程环境下既安全又高效。掌握它们的原理,对于设计线程安全的数据结构和排查并发 Bug 都非常重要。
在多线程环境下,选择正确的集合类至关重要。使用不当会导致数据竞争、性能瓶颈甚至严重的线程安全问题。以下是一些实用建议和典型场景,帮助开发者做出合理选择。
1. 明确读写比重
CopyOnWriteArrayList
或 CopyOnWriteArraySet
。ConcurrentHashMap
、ConcurrentSkipListMap
等。ConcurrentLinkedQueue
、BlockingQueue
等支持高并发写入的结构。2. 是否需要顺序保证
ConcurrentSkipListMap
(有序 Map)ConcurrentSkipListSet
(有序 Set)ConcurrentHashMap
更加高效,占用更少内存。3. 是否涉及阻塞行为
LinkedBlockingQueue
ArrayBlockingQueue
DelayQueue
、PriorityBlockingQueue
等take()
和 put()
方法,可以自然地控制线程等待和唤醒,避免手动加锁。4. 不要误用线程不安全集合
在并发场景中避免使用:
ArrayList
HashMap
LinkedList
HashSet
等这些集合在并发读写中没有任何线程安全机制,容易导致数据不一致、死循环(如旧版 HashMap
扩容)或程序崩溃。
5. 警惕性能陷阱
Collections.synchronizedXXX()
虽然提供了线程安全包装,但性能差,锁粒度粗,容易成为瓶颈。Hashtable
是遗留类,已被 ConcurrentHashMap
取代,避免使用。场景描述 | 推荐集合 |
---|---|
读多写少 | CopyOnWriteArrayList |
高并发读写 | ConcurrentHashMap |
有序并发访问 | ConcurrentSkipListMap / Set |
阻塞队列 / 消费者模型 | LinkedBlockingQueue, ArrayBlockingQueue |
简单线程安全包装(低性能) | Collections.synchronizedList 等 |
这部分还会更新扩充
Java集合框架是基于许多经典设计模式和原则来实现的,它通过不同的集合类和接口,为开发者提供了高效、灵活的数据存储解决方案。在深入分析Java集合框架时,我们可以发现其中有大量设计模式的应用以及源码设计思想的体现。
工厂模式(Factory Pattern)
Collections
类和 List
、Set
、Map
等接口的实现都遵循了工厂模式。例如,Collections
类提供了静态工厂方法(如 singletonList()
、emptyList()
等)来创建不可变集合对象。public static <T> List<T> singletonList(T o) {
return new SingletonList<>(o);
}
这里的 singletonList
就是工厂方法,通过封装集合的创建逻辑,提供特定类型的集合实例。
单例模式(Singleton Pattern)
EnumSet
是一个典型的单例模式应用。因为 EnumSet
只能处理枚举类型,所以它通过内部静态方法确保了集合的唯一性和单例模式。public static <E extends Enum<E>> EnumSet<E> of(E first, E... rest) {
return EnumSet.allOf(first.getDeclaringClass()).clone();
}
EnumSet
确保了枚举集合的单一性,不会创建多余的实例。
策略模式(Strategy Pattern)
TreeSet
和 TreeMap
中,元素的比较方式由 Comparator
接口策略化。Comparator
可以在运行时被替换,从而改变元素的排序方式。public TreeSet(Comparator<? super E> comparator) {
this.comparator = comparator;
}
TreeSet
和 TreeMap
允许用户通过传入不同的 Comparator
来决定集合的排序策略。
装饰者模式(Decorator Pattern)
List
、Set
和 Map
等集合接口有多个装饰类,如 Collections.unmodifiableList()
,它封装了一个集合对象,为集合提供只读功能。public static <T> List<T> unmodifiableList(List<? extends T> list) {
return new UnmodifiableList<>(list);
}
UnmodifiableList
是对原集合的装饰,使得外部无法修改该集合。
代理模式(Proxy Pattern)
CopyOnWriteArrayList
类使用了代理模式,它的行为被“复制”到一个新的副本中,以避免直接修改原集合时的并发问题。public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int length = elements.length;
// Create a new array for every modification.
Object[] newElements = Arrays.copyOf(elements, length + 1);
newElements[length] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
这个设计通过复制原数组来避免在并发修改集合时出现问题,从而使得集合在多线程环境下保持线程安全。
List
、Set
、Map
都是集合接口,具体的实现类如 ArrayList
、HashSet
、HashMap
等都有各自独立的实现。Collection
、List
、Set
、Map
等接口,集合框架能够灵活地支持不同类型的集合。在实现细节方面,诸如 AbstractList
和 AbstractSet
提供了基础的功能实现,让具体的实现类可以继承并加以拓展。add()
、remove()
、size()
)都遵循相同的约定,这使得开发者可以更轻松地切换不同类型的集合(如 ArrayList
与 LinkedList
)而不改变代码逻辑。HashMap
中,内部数组并不是在初始化时就创建,而是在需要时才进行扩容,确保内存的有效利用。Lazy
和 synchronized
在某些线程安全的集合类(如 CopyOnWriteArrayList
)中发挥作用,减少不必要的计算和开销。Java集合框架不仅仅提供了多种高效的数据结构和操作方法,它背后也体现了许多经典的设计模式和良好的源码设计思想。这些模式和思想的应用使得集合框架在不同的场景下都能提供灵活、高效、可扩展的解决方案。深入理解这些设计模式和源码实现,能够帮助开发者在使用集合框架时更加得心应手,同时也能够在设计自己的系统时,借鉴这些模式来提升代码的可维护性、可扩展性和性能。