引言
在 Java 的世界里,集合框架犹如一位无声的搭档,它伴随着我们编写的每一行代码,支撑着数据处理的核心逻辑。无论是电商系统中的购物车管理、社交网络的好友关系存储,还是大数据平台的数据聚合处理,Java 集合都扮演着不可或缺的角色。
然而,面对 ArrayList、LinkedList、HashMap、ConcurrentHashMap等众多选择,你是否曾感到困惑:
为什么有时候使用 ArrayList很快,有时候却很慢?
HashMap和 ConcurrentHashMap在并发场景下该如何选择?
LinkedList真的比 ArrayList更适合插入操作吗?
为什么遍历 CopyOnWriteArrayList时修改数据不会报错?
这些问题背后,隐藏着 Java 集合框架的深刻设计哲学。Java 集合不仅是数据存储的工具,更是算法思想、数据结构、并发控制和设计模式的完美结合。从 JDK 1.2 的初代集合框架,到如今 Java 21 中不断优化的并发容器,集合框架见证了 Java 语言的进化历程。
本文将带你深入 Java 集合的核心世界,我们将:
📚 系统梳理 - 从 List、Set、Map、Queue 四大体系出发,构建完整的知识脉络
🔍 深入源码 - 揭示 HashMap 的扩容机制、ArrayList 的动态增长原理
⚡ 性能对比 - 在不同场景下,如何选择最合适的集合类
🛡️ 并发安全 - 探索多线程环境下的最佳实践
💡 实战应用 - 结合真实业务场景,讲解集合的最佳使用姿势
无论你是刚接触 Java 的初学者,还是有一定经验的开发者,相信这篇文章都能帮助你:
彻底理解 Java 集合的内部工作原理
避免常见的性能陷阱和并发问题
在面试和实际工作中游刃有余地选择集合类型
编写出更高效、更健壮的 Java 代码
让我们从最基础的 "为什么需要集合" 开始,逐步深入这个既熟悉又神秘的 Java 核心领域。准备好你的思考,我们即将启程探索 Java 集合的精彩世界!
2.集合
2.1 Java 集合框架体系
bash
Collection (接口)
├── List (有序、可重复)
│ ├── ArrayList
│ ├── LinkedList
│ ├── Vector
│ └── Stack
├── Set (无序、唯一)
│ ├── HashSet
│ ├── LinkedHashSet
│ └── TreeSet
└── Queue (队列)
├── PriorityQueue
├── ArrayDeque
└── LinkedList
Map (接口)
├── HashMap
├── LinkedHashMap
├── TreeMap
├── Hashtable
└── ConcurrentHashMap
2.2 List 接口实现
2.2.1 ArrayList
特点:基于动态数组,随机访问快,增删慢
线程安全:非线程安全
扩容:默认初始容量10,按1.5倍扩容
java
List<String> list = new ArrayList<>();
list.add("Java");
list.get(0); // O(1)
2.2.2 LinkedList
特点:基于双向链表,增删快,随机访问慢
用途:频繁插入删除的场景
java
List<String> list = new LinkedList<>();
list.add("First");
list.addLast("Last"); // 特有方法
2.2.3 Vector
特点:线程安全的ArrayList(使用synchronized)
性能:较慢,已逐渐被淘汰
扩容:默认2倍扩容
java
import java.util.Vector;
public class VectorExample {
public static void main(String[] args) {
// 创建 Vector
Vector<String> vector = new Vector<>();
// 添加元素
vector.add("Java");
vector.add("Python");
vector.add("C++");
vector.add("JavaScript");
System.out.println("Vector元素: " + vector); // [Java, Python, C++, JavaScript]
// 获取元素
String first = vector.get(0);
System.out.println("第一个元素: " + first); // Java
// 删除元素
vector.remove("Python");
System.out.println("删除后: " + vector); // [Java, C++, JavaScript]
// 遍历元素
System.out.println("遍历Vector:");
for (String language : vector) {
System.out.println("- " + language);
}
}
}
2.2.4 CopyOnWriteArrayList
CopyOnWriteArrayList 是 Java 并发包 (java.util.concurrent) 中的一个线程安全的 List 实现。它采用 "写时复制" (Copy-On-Write) 策略来保证线程安全。
-
线程安全
读操作完全无锁
写操作使用锁保证线程安全
-
弱一致性迭代器
迭代器基于创建时的数组快照
迭代过程中不会反映后续的修改
-
写时复制
每次修改都创建新数组
原数组在修改期间保持不变
java
// 读操作时间复杂度:O(1)
public E get(int index) {
return get(getArray(), index); // 直接访问数组,无锁
}
// 写操作时间复杂度:O(n) - 需要复制整个数组
public boolean add(E e) {
synchronized (lock) {
Object[] elements = getArray();
int len = elements.length;
// 复制整个数组!这是性能瓶颈
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
}
}
适合使用场景:
✅ 读多写少
java
// 场景:配置信息缓存
CopyOnWriteArrayList<Config> configCache = new CopyOnWriteArrayList<>();
// 大量读取,极少修改
✅遍历操作频繁
java
// 场景:监听器列表
CopyOnWriteArrayList<EventListener> listeners = new CopyOnWriteArrayList<>();
// 经常遍历通知所有监听器
✅需要遍历时修改
java
// 不会抛出 ConcurrentModificationException
for (String item : list) {
if (condition) {
list.remove(item); // 安全操作
}
}
❌ 不适合场景
写操作频繁
大数据量集合(复制成本高)
实时性要求高(数据可能不是最新的)
List常见问题
从网上以及之前面试经历中收集了一些问题,例如 Java集合常见面试题总结(上)|Java Guid
ArrayList 和 Array (数组) 的区别?
- ArrayList 内部基于动态数组实现,比 Array (静态数组) 使用起来更加灵活:
- ArrayList 会根据实际存储的元素动态地扩容或缩容,而 Array 被创建之后就不能改变它的长度了。
- ArrayList 允许你使用泛型来确保类型安全,Array 则不可以。
- ArrayList 中只能存储对象。对于基本类型数据,需要使用其对应的包装类(如 Integer、Double 等)。Array 可以直接存储基本类型数据,也可以存储对象。
- ArrayList 支持插入、删除、遍历等常见操作,并且提供了丰富的 API 操作方法,比如 add() 、 remove() 等。Array 只是一个固定长度的数组,只能按照下标访问其中的元素,不具备动态添加、删除元素的能力。
- ArrayList 创建时不需要指定大小,而 Array 创建时必须指定大小。
2.3 Set 接口实现
2.3.1 HashSet
特点:基于HashMap实现,无序
性能:添加、删除、查询 O(1)
线程安全:非线程安全
java
Set<String> set = new HashSet<>();
set.add("apple");
set.add("banana");
2.3.2 LinkedHashSet
特点:保持插入顺序
底层:LinkedHashMap实现
java
Set<String> set = new LinkedHashSet<>();
2.3.3 TreeSet
特点:基于红黑树,元素有序
排序:自然排序或Comparator
java
Set<String> set = new TreeSet<>();
set.add("c");
set.add("a"); // 自动排序:["a", "c"]
2.4 Map 接口实现
2.4.1 HashMap
特点:数组+链表/红黑树,键值对存储
线程安全:非线程安全
重要参数:
初始容量:16
加载因子:0.75
链表转红黑树阈值:8
红黑树转链表阈值:6
java
Map<String, Integer> map = new HashMap<>();
map.put("age", 25);
map.get("age");
2.4.2 LinkedHashMap
特点:保持插入顺序或访问顺序
用途:LRU缓存实现
java
Map<String, Integer> map = new LinkedHashMap<>();
2.4.3 TreeMap
特点:基于红黑树,键有序
java
Map<String, Integer> map = new TreeMap<>();
2.4.4 ConcurrentHashMap
特点:线程安全的HashMap(分段锁/CAS)
推荐:多线程环境首选
java
Map<String, String> map = new ConcurrentHashMap<>();
2.5 Queue 接口实现
2.5.1 PriorityQueue
特点:优先级队列(最小堆)
java
Queue<Integer> queue = new PriorityQueue<>();
queue.offer(5);
queue.poll(); // 最小元素
2.5.2 ArrayDeque
特点:双端队列,数组实现
java
Deque<String> deque = new ArrayDeque<>();
deque.addFirst("first");
deque.addLast("last");
2.6 Java 集合类线程安全性对照表
| 非线程安全 | 线程安全替代 |
|---|---|
| ArrayList | CopyOnWriteArrayList |
| HashSet | CopyOnWriteArraySet |
| HashMap | ConcurrentHashMap |
| TreeMap | ConcurrentSkipListMap |
表格说明:
- CopyOnWriteArrayList: 写时复制,适合读多写少的场景
- CopyOnWriteArraySet: 基于 CopyOnWriteArrayList 实现
- ConcurrentHashMap: 分段锁/CAS 实现,性能较好
- ConcurrentSkipListMap: 跳表实现,键有序的并发 Map
使用示例:
java
// 线程安全的 List
List<String> list = new CopyOnWriteArrayList<>();
// 线程安全的 Map
Map<String, Object> map = new ConcurrentHashMap<>();
Java 集合类性能对比表
| 集合类 | 获取 | 添加 | 删除 | 内存 |
|---|---|---|---|---|
| ArrayList | O(1) | O(1)~O(n) | O(n) | 低 |
| LinkedList | O(n) | O(1) | O(1) | 高 |
| HashSet | O(1) | O(1) | O(1) | 中 |
| TreeSet | O(log n) | O(log n) | O(log n) | 高 |
| HashMap | O(1) | O(1) | O(1) | 中 |
时间复杂度符号说明
- O(1): 常数时间复杂度,性能最好
- O(log n): 对数时间复杂度,性能较好
- O(n): 线性时间复杂度,性能随数据量增长
- O(1)~O(n): 最好情况下为O(1),最坏情况下为O(n)
内存占用说明
- 低: 内存占用较少
- 中: 内存占用适中
- 高: 内存占用较高
各集合类特性总结
- ArrayList: 随机访问快,尾部添加快,但中间插入/删除慢
- LinkedList: 插入删除快,但随机访问慢
- HashSet: 基于哈希表,各项操作平均为O(1)
- TreeSet: 基于红黑树,保持元素有序,但操作需要O(log n)
- HashMap: 基于哈希表,键值对操作平均为O(1)
根据具体需求选择合适的集合类:
- 需要快速随机访问 → ArrayList
- 需要频繁插入删除 → LinkedList
- 需要去重且快速查找 → HashSet
- 需要有序集合 → TreeSet
- 需要键值对存储 → HashMap
选择建议
需要快速随机访问 → ArrayList
频繁插入删除 → LinkedList
去重且不关心顺序 → HashSet
去重且保持插入顺序 → LinkedHashSet
需要排序 → TreeSet/TreeMap
键值对存储 → HashMap
多线程环境 → ConcurrentHashMap, CopyOnWriteArrayList
先进先出队列 → ArrayDeque
优先级处理 → PriorityQueue
Java 集合框架非常丰富,根据不同的业务场景选择合适的集合类,可以显著提升程序性能和可维护性。