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[]),元素连续存储,支持索引访问。
线程安全 所有公共方法(如 addgetremove)均通过 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(属于集合框架)

七、适用场景与注意事项

适用场景:
  1. 多线程环境:需要线程安全的数组型集合(如共享数据的读写)。
  2. 遗留代码兼容:某些旧项目可能仍在使用 Vector
  3. 需要按索引频繁访问:如数组下标操作较多的场景(因支持 RandomAccess)。
避坑指南:
  1. 性能问题

    • 线程安全带来的锁开销会降低单线程性能,若无需线程安全,优先使用 ArrayList
    • 避免在单线程中使用 Vector,除非必须兼容旧代码。
  2. 扩容代价

    • 频繁扩容会导致大量数组复制操作,建议初始化时预估容量,减少扩容次数。
  3. 迭代器使用

    • 遍历时若需修改集合,需通过迭代器的 remove() 方法(与 ArrayList 一致),或手动同步(如 synchronized 块)。
  4. 替代方案

    • 多线程场景下,若需要更高性能,可考虑 CopyOnWriteArrayList(写时复制,适用于读多写少场景)。

八、总结

  • 本质:动态数组,线程安全,适合需要索引访问和线程安全的场景。
  • 核心优势:线程安全、随机访问高效、扩容策略可控。
  • 缺点:锁开销导致性能较低,单线程场景下不如 ArrayList
  • 最佳实践:仅在多线程环境或必须兼容旧代码时使用,否则优先选择 ArrayList 或并发容器(如 CopyOnWriteArrayList)。

为什么现在基本不用 Vector 了(机理)

关键在于锁的粒度:synchronized 加在方法级,锁的是整个 vector 对象——两个线程哪怕只是同时读getsize、甚至迭代遍历),也要排队互斥。而读操作本身并不修改数据,本可以并行。这就是"粗粒度锁"的代价:安全,但把并发度压到了 1。

现代替代方案的思路都是把锁变小

  • Collections.synchronizedList(list):同样是粗粒度锁,胜在能包装任意 List,性能与 Vector 相当,胜在语义清晰;
  • CopyOnWriteArrayList写时复制——写操作复制整个新数组,读操作完全无锁,读多写少场景吞吐量碾压 Vector;
  • ConcurrentLinkedQueue 等并发容器:无锁(CAS)算法,详见 03-Queue 系列。

一句话:Vector 解决的是"Java 还没有并发工具包的年代"的问题,如今它只是面试题里的"为什么不用"教科书。


⬅️ LinkedList 🏠 00-Java ➡️ 00-Set总览