发布于2026-06-02 阅读(0)
扫一扫,手机访问
List 是 Ja va 集合框架中一个非常核心的接口,它代表着有序、可重复的元素集合。说到有序,就好比排队,谁先来谁就排在前面,元素的位置由插入顺序决定。而可重复,则意味着你可以把同一个对象往里放多次。

在实践中,我们最常遇到的 List 实现有这么几个:ArrayList、LinkedList、Vector、Stack,以及并发场景下的 CopyOnWriteArrayList。每个都有自己独特的脾气和适用场景,搞清楚它们之间的区别,是写出高性能代码的基础。
底层结构
ArrayList 的底层,是一个动态数组。它初始化时的默认容量是 10,但这不是固定的。当元素数量超过当前容量时,它会触发扩容机制。扩容规则很简单:
新容量 = 旧容量 + 旧容量 / 2,也就是 1.5 倍。例如容量 10 的数组,扩一次变成 15,再扩一次变成 22,以此类推。
时间复杂度
| 操作 | 复杂度 | 说明 |
|---|---|---|
| 随机访问 get(i) | O(1) | 直接通过数组下标访问 |
| 尾部 add | O(1) 均摊 | 扩容时退化为 O(n) |
| 中间插入/删除 | O(n) | 需要移动后续所有元素 |
线程安全
ArrayList 是非线程安全的。如果多个线程同时对它进行结构修改(比如一个线程在遍历,另一个在添加/删除),很容易出问题。解决方案通常用 Collections.synchronizedList 进行包装,或者直接上并发包里的 CopyOnWriteArrayList。
底层结构
LinkedList 的底层是双向链表。每个节点由三部分组成:前驱指针(prev)、存储的数据(item)、后继指针(next)。这意味着它的内存是分散的,不连续。
时间复杂度
| 操作 | 复杂度 | 说明 |
|---|---|---|
| get(i) | O(n) | 需要从头或尾遍历到指定位置 |
| 头/尾插入删除 | O(1) | 只需要修改指针指向 |
| 中间插入 | O(n) | 先要找到那个位置 |
底层结构
Vector 的底层也是动态数组,这一点和 ArrayList 几乎一样。但它有一个关键区别:线程安全。
关键区别
Vector 的所有方法都加了 synchronized,这意味着它在多线程环境下是安全的。
问题
不过,这种锁粒度太粗了,性能非常差。实际项目中,几乎没有理由再去使用 Vector,它已经被 JCF 体系淘汰了。能用 ArrayList 或者并发集合解决的,坚决不用 Vector。
继承关系
Stack extends Vector
特点
Stack 是典型的后进先出(LIFO)结构。因为它继承了 Vector,所以也是线程安全的。但同样的,由于 Vector 本身的问题,Stack 也属于“已淘汰”的组件。更推荐用 Deque 接口的实现类(比如 ArrayDeque)来模拟栈的行为。
底层思想
CopyOnWriteArrayList 是并发包下的一个利器,它的核心思想是“写时复制”。
特点
| 方面 | 说明 |
|---|---|
| 线程安全 | ✅ |
| 读性能 | 非常高 |
| 写性能 | 较差(每次写都要复制整个底层数组) |
| 迭代 | 不会抛 ConcurrentModificationException |
它的读操作完全不加锁,读的是快照数组,所以读性能极好。但写的代价也很高——每次修改(add、set、remove)都会先复制一份新数组,修改完后再将引用指向新数组。因此它非常适合“读多写少”的并发场景,比如缓存配置、黑白名单等。
| 实现 | 底层 | 线程安全 | 适合场景 |
|---|---|---|---|
| ArrayList | 动态数组 | ❌ | 随机查询多、数据变化不频繁 |
| LinkedList | 双向链表 | ❌ | 频繁的头尾插入删除操作 |
| Vector | 动态数组 | ✅ | 已淘汰,不建议使用 |
| Stack | 栈 | ✅ | 已淘汰,用 Deque 代替 |
| CopyOnWriteArrayList | 数组复制 | ✅ | 高并发读多写少场景 |
Q1:ArrayList 和 LinkedList 区别?
ArrayList 底层是数组,查询快(O(1)),插入慢(O(n));
LinkedList 底层是链表,头尾操作快(O(1)),随机访问慢(O(n))。
Q2:为什么 ArrayList 不是线程安全?
在并发环境下,多个线程同时调用 add / remove 可能导致:
扩容时数据丢失、元素覆盖、操作被中断导致数据不一致。
Q3:CopyOnWriteArrayList 为什么读快?
读操作没有加锁,始终读取的是当前底层数组的一个稳定快照,所以不会出现并发修改异常(ConcurrentModificationException)。
Q4:为什么不推荐 Vector / Stack?
因为它们的 synchronized 锁粒度太大,性能差,且其他并发容器(如 CopyOnWriteArrayList、ConcurrentLinkedDeque)提供了更好的替代方案。
ArrayList 查得快,LinkedList 头尾插得快,
并发专用 CopyOnWrite,Vector 和 Stack 让它歇。
| 对比点 | List | Set |
|---|---|---|
| 是否有序 | ✅ 有序(按插入顺序) | ❌ 大多无序(LinkedHashSet 例外) |
| 是否允许重复 | ✅ 允许 | ❌ 不允许 |
| 是否有下标 | ✅ 有(get(i)) | ❌ 没有 |
| 常见实现 | ArrayList、LinkedList | HashSet、LinkedHashSet、TreeSet |
| 典型用途 | 保存“有顺序、可重复”的数据 | 保存“去重”的数据 |
一句话记忆:
List = 有顺序 + 可重复
Set = 去重
Listlist = new ArrayList<>(); list.add(10); list.add(10); list.add(20); System.out.println(list); // [10, 10, 20] System.out.println(list.get(1)); // 10
Setset = new HashSet<>(); set.add(10); set.add(10); set.add(20); System.out.println(set); // [10, 20]
依赖两个方法:hashCode() + equals()。
List 和 Set 都是 Collection 的子接口,核心区别在于:是否允许重复、是否有顺序、是否有下标。List 适合按顺序存储可重复数据,Set 天生去重、无下标,完全依赖哈希值判断唯一性。
List:顺序 + 重复 + 下标
Set:去重 + 无下标 + 基于 equals / hashCode
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8