深度解析Vector工作原理

265 阅读7分钟

引言

在 Java 的集合框架中,Vector 是一个历史悠久且功能强大的类。它和 ArrayList 类似,都是动态数组的实现,但 Vector 具有线程安全的特性。在多线程环境下,若需要对数组进行操作,Vector 是一个不错的选择。本文将详细探讨 Vector 的原理,涵盖其底层数据结构、核心属性、构造方法、常用操作的实现细节、性能特点以及应用场景。

1. Vector 概述

1.1 定义与用途

Vector 类实现了可动态增长的对象数组,它位于 java.util 包中。Vector 可以存储任意类型的对象,并且能根据需要自动调整自身的大小。由于它是线程安全的,因此在多线程环境中,当多个线程需要同时访问和修改数组时,使用 Vector 可以避免数据不一致的问题。

1.2 继承关系与实现接口

Vector 继承自 AbstractList 类,并实现了 ListRandomAccessCloneablejava.io.Serializable 接口。这表明 Vector 具有列表的基本特性,支持随机访问,能够进行克隆操作,同时还可以进行序列化和反序列化。

import java.util.Vector;
import java.util.List;

public class VectorOverview {
    public static void main(String[] args) {
        // 创建一个 Vector 对象
        Vector<String> vector = new Vector<>();
        // 可以将其赋值给 List 接口类型的变量
        List<String> list = vector;
    }
}

2. 底层数据结构:动态数组

2.1 动态数组的基本概念

Vector 基于动态数组实现,这意味着它在内存中是一段连续的存储空间。当元素数量超过数组的初始容量时,Vector 会自动进行扩容,以容纳更多的元素。

2.2 Vector 中的数组属性

Vector 中,有一个核心的数组属性用于存储元素:

protected Object[] elementData;

elementData 是一个 Object 类型的数组,用于存储 Vector 中的元素。

3. 核心属性

除了 elementData 数组外,Vector 还有几个重要的核心属性:

protected int elementCount;
protected int capacityIncrement;
  • elementCount:表示 Vector 中实际存储的元素数量。
  • capacityIncrement:表示当 Vector 需要扩容时,每次增加的容量大小。如果该值为 0,则每次扩容时容量会翻倍。

4. 构造方法

4.1 无参构造方法

public Vector() {
    this(10);
}

无参构造方法会调用带一个参数的构造方法,创建一个初始容量为 10 的 Vector

4.2 指定初始容量的构造方法

public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}

该构造方法允许指定 Vector 的初始容量,同时将 capacityIncrement 设置为 0。

4.3 指定初始容量和容量增量的构造方法

public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
}

此构造方法允许同时指定 Vector 的初始容量和每次扩容时增加的容量大小。

4.4 带集合参数的构造方法

public Vector(Collection<? extends E> c) {
    elementData = c.toArray();
    elementCount = elementData.length;
    // c.toArray might (incorrectly) not return Object[] (see 6260652)
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}

该构造方法接受一个集合作为参数,将集合中的元素复制到 Vector 中。

import java.util.ArrayList;
import java.util.Vector;
import java.util.List;

public class VectorConstructors {
    public static void main(String[] args) {
        // 无参构造方法
        Vector<String> vector1 = new Vector<>();

        // 指定初始容量的构造方法
        Vector<String> vector2 = new Vector<>(20);

        // 指定初始容量和容量增量的构造方法
        Vector<String> vector3 = new Vector<>(15, 5);

        // 带集合参数的构造方法
        List<String> arrayList = new ArrayList<>();
        arrayList.add("apple");
        arrayList.add("banana");
        Vector<String> vector4 = new Vector<>(arrayList);
        System.out.println(vector4);
    }
}

5. 常用操作原理

5.1 添加元素

5.1.1 在尾部添加元素

public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}
  • add(E e) 方法是线程安全的,使用 synchronized 关键字进行同步。
  • 首先调用 ensureCapacityHelper(elementCount + 1) 方法确保 Vector 有足够的容量来存储新元素。
  • 然后将新元素添加到数组的末尾,并将 elementCount 加 1。

5.1.2 在指定位置插入元素

public synchronized void add(int index, E element) {
    insertElementAt(element, index);
}

public synchronized void insertElementAt(E obj, int index) {
    modCount++;
    if (index > elementCount) {
        throw new ArrayIndexOutOfBoundsException(index
                                                 + " > " + elementCount);
    }
    ensureCapacityHelper(elementCount + 1);
    System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
    elementData[index] = obj;
    elementCount++;
}
  • add(int index, E element) 方法调用 insertElementAt(element, index) 方法在指定位置插入元素。
  • 首先检查索引的有效性,然后确保 Vector 有足够的容量。
  • 使用 System.arraycopy 方法将指定位置及之后的元素向后移动一位,最后将新元素插入到指定位置。

5.2 访问元素

public synchronized E get(int index) {
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);

    return elementData(index);
}

E elementData(int index) {
    return (E) elementData[index];
}
  • get(int index) 方法是线程安全的,首先检查索引是否越界,然后返回指定位置的元素。

5.3 修改元素

public synchronized E set(int index, E element) {
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}
  • set(int index, E element) 方法是线程安全的,首先检查索引是否越界,然后将指定位置的元素替换为新元素,并返回旧元素。

5.4 删除元素

5.4.1 删除指定位置的元素

public synchronized E remove(int index) {
    modCount++;
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);
    E oldValue = elementData(index);

    int numMoved = elementCount - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--elementCount] = null; // Let gc do its work

    return oldValue;
}
  • remove(int index) 方法是线程安全的,首先检查索引是否越界,然后将指定位置的元素删除。
  • 使用 System.arraycopy 方法将指定位置之后的元素向前移动一位,最后将数组末尾的元素置为 null,以便垃圾回收。

5.4.2 删除指定元素

public boolean remove(Object o) {
    return removeElement(o);
}

public synchronized boolean removeElement(Object obj) {
    modCount++;
    int i = indexOf(obj);
    if (i >= 0) {
        removeElementAt(i);
        return true;
    }
    return false;
}
  • remove(Object o) 方法调用 removeElement(o) 方法删除指定元素。
  • 首先找到指定元素的索引,然后调用 removeElementAt(i) 方法删除该元素。

6. 扩容机制

Vector 的元素数量超过其当前容量时,会触发扩容操作。扩容的具体实现如下:

private void ensureCapacityHelper(int minCapacity) {
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}
  • ensureCapacityHelper(int minCapacity) 方法用于检查是否需要扩容。
  • grow(int minCapacity) 方法进行具体的扩容操作。如果 capacityIncrement 大于 0,则新容量为旧容量加上 capacityIncrement;否则,新容量为旧容量的两倍。
  • 如果新容量超过 MAX_ARRAY_SIZE,则调用 hugeCapacity(minCapacity) 方法处理。

7. 性能分析

7.1 时间复杂度

  • 插入和删除操作:在尾部插入和删除元素的平均时间复杂度为 O(1),但在指定位置插入和删除元素的平均时间复杂度为 O(n),因为需要移动元素。
  • 访问操作:访问指定位置的元素的时间复杂度为 O(1),因为支持随机访问。

7.2 空间复杂度

Vector 的空间复杂度为 O(n),主要用于存储数组元素。由于其线程安全的特性,会有一些额外的同步开销。

8. 应用场景

8.1 多线程环境

在多线程环境下,当多个线程需要同时对数组进行读写操作时,Vector 的线程安全特性可以保证数据的一致性。

import java.util.Vector;

public class VectorMultiThreadExample {
    private static Vector<Integer> vector = new Vector<>();

    public static void main(String[] args) {
        // 线程 1:添加元素
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                vector.add(i);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 线程 2:读取元素
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                if (!vector.isEmpty()) {
                    System.out.println(vector.get(0));
                }
                try {
                    Thread.sleep(150);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

8.2 需要频繁随机访问元素的场景

由于 Vector 支持随机访问,在需要频繁根据索引访问元素的场景下,使用 Vector 可以提高访问效率。

9. 总结

Vector 作为 Java 集合框架中的一员,基于动态数组实现,具有线程安全的特性。它适用于多线程环境下对数组进行操作,以及需要频繁随机访问元素的场景。然而,由于其线程安全是通过 synchronized 关键字实现的,会带来一定的性能开销。在单线程环境下,ArrayList 通常是更好的选择。通过深入理解 Vector 的原理和性能特点,我们可以在实际开发中更加合理地选择和使用数据结构。