Vector
定位说明
List 系列第 3 篇,也是历史遗留篇(Java 1.0 时代产物)。读它的正确姿势:理解"方法级 synchronized 粗粒度锁"的代价(见篇内"为什么现在基本不用 Vector 了"),明白为什么新代码一律 ArrayList、需要并发时选 CopyOnWriteArrayList / Collections.synchronizedList()。它的子类 Stack(栈)同样遗留,新代码用 Deque(如 ArrayDeque)替代。扩容 2 倍 vs ArrayList 1.5 倍的对比见本篇第六节。
一、基本概念
-
定义:
Vector是 Java 中 基于动态数组实现的线程安全集合类,位于java.util包中。它早期属于 Java 集合框架(JCF)的一部分,功能与ArrayList类似,但具有线程安全特性。 -
类继承结构:
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- 实现
List接口,支持有序、可重复元素。 - 实现
RandomAccess接口,支持快速随机访问(通过索引)。 - 线程安全:通过
synchronized方法保证多线程下的安全操作。
二、本质:动态数组实现
-
底层结构:
Vector本质是一个 可扩容的 Object 数组,元素连续存储在内存中,每个元素通过索引访问。 -
核心属性:
protected Object[] elementData; // 存储元素的数组
protected int elementCount; // 实际元素数量
protected int capacityIncrement; // 扩容时的增量(默认为 0,即按倍数扩容)
-
扩容机制:
当元素数量超过数组容量时,Vector 会自动扩容:
- 默认扩容策略:若
capacityIncrement为 0,新容量为 原容量的 2 倍; - 若指定了
capacityIncrement,新容量为 原容量 + capacityIncrement。 - 扩容方法:
grow(int minCapacity),会创建新数组并复制原有元素,时间复杂度为 O(n)。
- 默认扩容策略:若
三、核心特性
| 特性 | 说明 |
|---|---|
| 底层结构 | 动态数组(Object[]),元素连续存储,支持索引访问。 |
| 线程安全 | 所有公共方法(如 add、get、remove)均通过 synchronized 修饰,保证线程安全。 |
| 增删效率 | - 尾部添加:O (1)(若无需扩容); - 中间插入 / 删除:O (n)(需移动元素)。 |
| 查询效率 | 随机访问 O (1)(通过索引直接定位)。 |
| 容量与扩容 | 初始容量可指定,默认 10;扩容时默认按 2 倍增长(或按 capacityIncrement)。 |
| 元素限制 | 允许存储 null,但建议避免(与 ArrayList 一致)。 |
| 迭代器行为 | 支持 fail-fast 机制(与 ArrayList 类似),遍历时修改结构会抛出异常。 |
四、常用方法与示例
1. 创建方式
// 默认容量 10
Vector<String> vector1 = new Vector<>();
// 指定初始容量
Vector<Integer> vector2 = new Vector<>(20);
// 指定初始容量和扩容增量
Vector<Double> vector3 = new Vector<>(10, 5);
2. 核心操作
| 操作类型 | 方法示例 | 时间复杂度 | 说明 |
|---|---|---|---|
| 添加元素 | add(E e) |
O (1)(均摊) | 尾部添加,满容量时触发扩容。 |
add(int index, E element) |
O(n) | 中间插入,需移动后续元素。 | |
| 删除元素 | remove(int index) |
O(n) | 删除指定索引元素,移动后续元素。 |
remove(Object o) |
O(n) | 按值删除,需遍历查找元素。 | |
| 查询元素 | get(int index) |
O(1) | 通过索引快速获取元素。 |
| 修改元素 | set(int index, E element) |
O(1) | 替换指定索引位置的元素。 |
| 获取容量 | capacity() |
O(1) | 返回底层数组的总容量。 |
| 获取大小 | size() |
O(1) | 返回实际元素数量。 |
3. 线程安全操作示例
Vector<String> safeVector = new Vector<>();
// 多线程安全添加
safeVector.add("element");
// 遍历(需手动同步迭代器,否则可能 ConcurrentModificationException)
synchronized (safeVector) { // 显式加锁保证遍历安全
for (String item : safeVector) {
System.out.println(item);
}
}
五、底层实现细节
1. 添加元素(尾部)
public synchronized boolean add(E e) {
modCount++; // 记录结构修改次数(用于 fail-fast)
ensureCapacityHelper(elementCount + 1); // 检查容量,不足则扩容
elementData[elementCount++] = e; // 直接赋值,O(1)
return true;
}
private void ensureCapacityHelper(int minCapacity) {
if (minCapacity - elementData.length > 0) {
grow(minCapacity); // 触发扩容
}
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); // 扩容策略
newCapacity = Math.max(newCapacity, minCapacity); // 确保新容量足够
elementData = Arrays.copyOf(elementData, newCapacity); // 复制数组,O(n)
}
2. 删除元素(指定索引)
public synchronized E remove(int index) {
modCount++;
rangeCheck(index); // 检查索引越界
E oldValue = elementData(index); // 获取旧值
int numMoved = elementCount - index - 1;
if (numMoved > 0) {
System.arraycopy(elementData, index + 1, elementData, index, numMoved); // 移动后续元素,O(n)
}
elementData[--elementCount] = null; // 清空最后一个位置,帮助 GC
return oldValue;
}
六、与 ArrayList 的对比
| 对比维度 | Vector | ArrayList |
|---|---|---|
| 线程安全 | 是(方法加 synchronized) |
否 |
| 扩容策略 | 默认扩容为 2 倍(或按 capacityIncrement) |
默认扩容为 1.5 倍 |
| 性能 | 较低(锁开销) | 较高(无锁) |
| 迭代器安全性 | 支持 fail-fast | 支持 fail-fast |
| 适用场景 | 多线程环境,需要线程安全 | 单线程环境,追求高性能 |
| 设计年代 | Java 1.0(早期类) | Java 1.2(属于集合框架) |
七、适用场景与注意事项
适用场景:
- 多线程环境:需要线程安全的数组型集合(如共享数据的读写)。
- 遗留代码兼容:某些旧项目可能仍在使用
Vector。 - 需要按索引频繁访问:如数组下标操作较多的场景(因支持
RandomAccess)。
避坑指南:
-
性能问题:
- 线程安全带来的锁开销会降低单线程性能,若无需线程安全,优先使用
ArrayList。 - 避免在单线程中使用
Vector,除非必须兼容旧代码。
- 线程安全带来的锁开销会降低单线程性能,若无需线程安全,优先使用
-
扩容代价:
- 频繁扩容会导致大量数组复制操作,建议初始化时预估容量,减少扩容次数。
-
迭代器使用:
- 遍历时若需修改集合,需通过迭代器的
remove()方法(与ArrayList一致),或手动同步(如synchronized块)。
- 遍历时若需修改集合,需通过迭代器的
-
替代方案:
- 多线程场景下,若需要更高性能,可考虑
CopyOnWriteArrayList(写时复制,适用于读多写少场景)。
- 多线程场景下,若需要更高性能,可考虑
八、总结
- 本质:动态数组,线程安全,适合需要索引访问和线程安全的场景。
- 核心优势:线程安全、随机访问高效、扩容策略可控。
- 缺点:锁开销导致性能较低,单线程场景下不如
ArrayList。 - 最佳实践:仅在多线程环境或必须兼容旧代码时使用,否则优先选择
ArrayList或并发容器(如CopyOnWriteArrayList)。
为什么现在基本不用 Vector 了(机理)
关键在于锁的粒度:synchronized 加在方法级,锁的是整个 vector 对象——两个线程哪怕只是同时读(get、size、甚至迭代遍历),也要排队互斥。而读操作本身并不修改数据,本可以并行。这就是"粗粒度锁"的代价:安全,但把并发度压到了 1。
现代替代方案的思路都是把锁变小:
Collections.synchronizedList(list):同样是粗粒度锁,胜在能包装任意 List,性能与 Vector 相当,胜在语义清晰;CopyOnWriteArrayList:写时复制——写操作复制整个新数组,读操作完全无锁,读多写少场景吞吐量碾压 Vector;ConcurrentLinkedQueue等并发容器:无锁(CAS)算法,详见 03-Queue 系列。
一句话:Vector 解决的是"Java 还没有并发工具包的年代"的问题,如今它只是面试题里的"为什么不用"教科书。
⬅️ LinkedList 🏠 00-Java ➡️ 00-Set总览
💬 评论