--- title: "03-Vector" created: 2025-12-02 tags: - Java --- # Vector > [!note] 定位说明 > 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` 类似,但具有线程安全特性。 - **类继承结构**: ```java public class Vector extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable ``` - 实现 `List` 接口,支持有序、可重复元素。 - 实现 `RandomAccess` 接口,支持快速随机访问(通过索引)。 - **线程安全**:通过 `synchronized` 方法保证多线程下的安全操作。 #### **二、本质:动态数组实现** - **底层结构**: `Vector` 本质是一个 **可扩容的 Object 数组**,元素连续存储在内存中,每个元素通过索引访问。 - 核心属性: ```java 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. 创建方式** ```java // 默认容量 10 Vector vector1 = new Vector<>(); // 指定初始容量 Vector vector2 = new Vector<>(20); // 指定初始容量和扩容增量 Vector 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. 线程安全操作示例** ```java Vector safeVector = new Vector<>(); // 多线程安全添加 safeVector.add("element"); // 遍历(需手动同步迭代器,否则可能 ConcurrentModificationException) synchronized (safeVector) { // 显式加锁保证遍历安全 for (String item : safeVector) { System.out.println(item); } } ``` #### **五、底层实现细节** ##### **1. 添加元素(尾部)** ```java 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. 删除元素(指定索引)** ```java 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(属于集合框架) | #### **七、适用场景与注意事项** ##### **适用场景:** 1. **多线程环境**:需要线程安全的数组型集合(如共享数据的读写)。 2. **遗留代码兼容**:某些旧项目可能仍在使用 `Vector`。 3. **需要按索引频繁访问**:如数组下标操作较多的场景(因支持 `RandomAccess`)。 ##### **避坑指南:** 1. **性能问题**: - 线程安全带来的锁开销会降低单线程性能,若无需线程安全,优先使用 `ArrayList`。 - 避免在单线程中使用 `Vector`,除非必须兼容旧代码。 2. **扩容代价**: - 频繁扩容会导致大量数组复制操作,建议初始化时预估容量,减少扩容次数。 3. **迭代器使用**: - 遍历时若需修改集合,需通过迭代器的 `remove()` 方法(与 `ArrayList` 一致),或手动同步(如 `synchronized` 块)。 4. **替代方案**: - 多线程场景下,若需要更高性能,可考虑 `CopyOnWriteArrayList`(写时复制,适用于读多写少场景)。 #### **八、总结** - **本质**:动态数组,线程安全,适合需要索引访问和线程安全的场景。 - **核心优势**:线程安全、随机访问高效、扩容策略可控。 - **缺点**:锁开销导致性能较低,单线程场景下不如 `ArrayList`。 - **最佳实践**:仅在多线程环境或必须兼容旧代码时使用,否则优先选择 `ArrayList` 或并发容器(如 `CopyOnWriteArrayList`)。 ### 为什么现在基本不用 Vector 了(机理) 关键在于锁的粒度:`synchronized` 加在**方法级**,锁的是整个 vector 对象——两个线程哪怕只是**同时读**(`get`、`size`、甚至迭代遍历),也要排队互斥。而读操作本身并不修改数据,本可以并行。这就是"粗粒度锁"的代价:安全,但把并发度压到了 1。 现代替代方案的思路都是**把锁变小**: - `Collections.synchronizedList(list)`:同样是粗粒度锁,胜在能包装任意 List,性能与 Vector 相当,胜在语义清晰; - `CopyOnWriteArrayList`:**写时复制**——写操作复制整个新数组,读操作完全无锁,读多写少场景吞吐量碾压 Vector; - `ConcurrentLinkedQueue` 等并发容器:无锁(CAS)算法,详见 03-Queue 系列。 一句话:Vector 解决的是"Java 还没有并发工具包的年代"的问题,如今它只是面试题里的"为什么不用"教科书。 --- ⬅️ [[02-LinkedList|LinkedList]] 🏠 [[00-Java|00-Java]] ➡️ [[00-Set总览|00-Set总览]]