Java容器深度总结:Vector

原地徘徊一千步,也抵不上迈出一步。心中想无数次,也不如真正行动一次。

1.Vector概述

Vector,来自于JDK1.0 的古老集合类,继承自 AbstractList,实现了 List 接口 ,底层是数组结构,元素可重复,有序(存放顺序),支持下标索引访问,允许null元素。

Vector所有方法的实现都是同步的(被synchronized修饰),数据安全,因此效率低,可以看成ArrayList的同步版本。Vector与ArrayList十分相似,所以建议先阅读:Java容器深度总结:ArrayList

Vector类图:
在这里插入图片描述
Vector定义:

public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable

Vector与ArrayList在定义上几乎一致:

  • Vector< E > :Vector支持泛型。
  • 继承 AbstractList< E >:继承AbstractList,此类提供 List 接口的骨干实现。
  • 实现 List< E >:实现了List接口,该接口包含了有关列表的基本操作。
  • 实现RandomAccess, Cloneable, java.io.Serializable接口:表明Vector具备快速随机访问能力,可以调用clone()克隆,实现序列化。
2.Vector主要成员变量
  • protected Object[] elementData : 存放元素的底层容器,为Object类型的数组,数组长度为Vector的容量。
  • protected int elementCount:实际保存元素的个数。
  • protected int capacityIncrement:容量增量,用于扩容时计算新容量大小。
3.构造函数和初始容量

Vector的构造方法有以下四个:

  • Vector()
  • Vector(int initialCapacity)
  • Vector(int initialCapacity, int capacityIncrement)
  • Vector(Collection<? extends E> c)
3.1 Vector()

Vector无参构造调用了Vector(int initialCapacity)构造函数,初始化数据数组(elementData)的容量为10,容量增量(capacityIncrement)为0

public Vector() {
    this(10); // 调用Vector(int initialCapacity)构造函数,初始容量10
}
3.2 Vector(int initialCapacity)

Vector(int initialCapacity)调用Vector(int initialCapacity, int capacityIncrement)构造函数,自定义数组容量,容量增量(capacityIncrement)为0

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

创建指定容量的数组,指定容量增量(capacityIncrement)的值。

public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
}
  • initialCapacity 初始化容量小于0时抛出异常。
  • 创建 initialCapacity 容量的数组。
  • 赋值容量增量(capacityIncrement )。
3.4 Vector(Collection<? extends E> c)

构建一个包含指定 Collection 中的元素的集合,数组容量和元素个数为Collection 集合的元素个数,容量增量为0(未指定容量增量,默认值为0),并且这些元素按其 collection 的迭代器返回元素的顺序排列。

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);
}
  • 将Collection集合转为数组赋值给elementData。
  • 赋值元素个数(elementCount )为elementData的长度。
  • 若elementData类型不是Object,则转化成Object数组。
4.Vector扩容机制

Vector扩容机制与ArrayList相似,比ArrayList的扩容机制简单一些。Vector添加元素时,由ensureCapacityHelper()方法保证容量足够。

Vector扩容的执行流程:

  1. 判断是否需要扩容;
  2. 计算新容量;
  3. 考虑数组长度溢出;
  4. 扩容
4.1 判断是否需要扩容

当所需要的最小容量(minCapacity)大于数组的容量时,则调用grow()方法进行扩容。

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

Tip:最小容量(minCapacity)为添加元素后的总元素个数。

4.2 计算新容量

确定要进行扩容后,先计算扩容后数组的新容量。

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);
}

注意:

新容量的计算方法:newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);

  • 当未指定容量增量(capacityIncrement )或 capacityIncrement <= 0 时,新容量(newCapacity )为 旧容量(oldCapacity )的2倍。
  • 否则(容量增量 > 0 时),新容量(newCapacity )为 旧容量和容量增量的和。
  • 若计算出来的新容量(newCapacity )小于最小容量(minCapacity),则新容量取最小容量。

Tip: ArrayList扩容时,新容量约为旧容量的 1.5 倍。

4.3 考虑数组长度溢出

不管是计算出的新容量(newCapacity ),还是最小容量(minCapacity)都存在数值溢出的情况,Vector考虑数组长度溢出和ArrayList相同。

// 考虑数组长度溢出
if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);
// MAX_ARRAY_SIZE  : 建议最大数组容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}
  • 当minCapacity溢出时抛出OutOfMemoryError异常;
  • minCapacity > MAX_ARRAY_SIZE 时 设置 newCapacity 等于Integer.MAX_VALUE。
  • minCapacity <= MAX_ARRAY_SIZE 时 设置 newCapacity 等于 MAX_ARRAY_SIZE。
4.4 扩容

通过Arrays.copyOf()重新构建一个数组,然后将原来数组的元素拷贝到新数组中,新数组的长度为计算出的新容量(newCapacity)。并修改原数组的引用指向这个新建数组,原数组自动抛弃(Java垃圾回收机制会自动回收)。

//扩容
elementData = Arrays.copyOf(elementData, newCapacity);
4.5 Vector扩容总结

Vector添加元素时使用 ensureCapacityHelper() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,当未指定容量增量时,默认新容量为旧容量的2倍,指定容量增量时,新容量为旧容量与容量增量的和。

扩容操作需要调用 Arrays.copyOf() 把原数组整个复制到新数组中,这个操作代价很高,因此应该尽量减少扩容次数。

  • 创建Vector对象时,可以指定大概容量。
  • add之前调用ensureCapacity()方法指定大概容量。
  • 根据实际情况,选择是否指定容量增量和容量增量大小。
5.常用方法

Vector所有的方法的实现都是同步的。

签名描述时间复杂度
E elementAt(int index)获取指定位置的元素O(1)
void addElement(E obj)在集合末尾新增一个元素O(1)
void add(int index, E element)在指定位置添加元素O(n)
boolean addAll(int index, Collection<? extends E> c)在指定位置添加集合中的元素O(n)
void removeElementAt(int index)删除指定位置的元素O(n)
boolean removeElement(Object obj)移除集合中第一次出现的指定元素O(n)
void removeAllElements()从列表中移除所有元素O(n)
void insertElementAt(E obj, int index)在指定索引位置中插入元素O(n)
E set(int index, E element)设置指定位置的元素值O(1)
boolean contains(Object o)是否包含某个元素O(n)
int size()返回列表中的元素个数O(1)
boolean isEmpty()列表是否为空O(1)
6.Enumeration迭代器

由于Vector属于List接口集合体系,因此具有通用的iterator 和 listIterator迭代器,同时Vector还具有Enumeration迭代器。

Enumeration迭代器是同Vector在JDK1.0就存在了,因此比较古老,功能单一:

public Enumeration<E> elements() {
    
    return new Enumeration<E>() {
        int count = 0;

        public boolean hasMoreElements() {
            return count < elementCount;
        }

        public E nextElement() {
            synchronized (Vector.this) {
                if (count < elementCount) {
                    return elementData(count++);
                }
            }
            throw new NoSuchElementException("Vector Enumeration");
        }
    };
}

Enumeration是一个接口,Vector中的elements()方法返回了Enumeration接口的匿名实现类。

Enumeration迭代器特点:

  • 只能正向迭代。
  • 只能用于迭代,没有其他额外的功能。
  • 没有快速失败机制(fail-fast)。

由于Enumeration迭代器没有iterator和listIterator迭代器的快速失败机制,因此不会抛出并发修改的异常(ConcurrentModificationException),因此容易产生一些比较严重的问题:

public class VectorTest {

    public static void main(String[] args) {
        Vector vector = new Vector(Arrays.asList(1,2,3,4));
        //获取自己的迭代器Enumeration
        Enumeration elements = vector.elements();
        while (elements.hasMoreElements()) {
            vector.add(1);
            System.out.println(elements.nextElement());
        } // 死循环
    }
}

用于Enumeration迭代器通过内部的count变量和Vector的元素个数elementCount比较来判断是否能继续迭代,因此在每次迭代时添加元素,将会导致elementCount永远比count大,从而造成死循环,直到OutOfMemory。

注意:Iterator比Enumeration覆盖范围广,功能更强大。因此迭代时尽量使用Iterator和ListIterator。

7.快速失败(fail-fast)

Vector也是快速失败(fail-fast)的,其实现原理和ArrayList中的相同,此处不再赘述。

8.序列化

同ArrayList一样,Vector也实现了writeObject()和readObject()方法,区别是Vector的内部数组(elementData)被全部序列化了,而ArrayList只序列化了elementData中保存元素的那部分。

Vector:

private void writeObject(java.io.ObjectOutputStream s)
            throws java.io.IOException {
   
    final java.io.ObjectOutputStream.PutField fields = s.putFields();
    final Object[] data;
    synchronized (this) {
        fields.put("capacityIncrement", capacityIncrement);
        fields.put("elementCount", elementCount);
        data = elementData.clone();
    }
    fields.put("elementData", data);
    s.writeFields();
}
    
private void readObject(ObjectInputStream in)
        throws IOException, ClassNotFoundException {
   
    ObjectInputStream.GetField gfields = in.readFields();
    int count = gfields.get("elementCount", 0);
    Object[] data = (Object[])gfields.get("elementData", null);
    if (count < 0 || data == null || count > data.length) {
        throw new StreamCorruptedException("Inconsistent vector internals");
    }
    elementCount = count;
    elementData = data.clone();
}
9.clone机制

Vector的clone()方法返回的是一个全新的Vector实例对象,但是其elementData,也就是存储数据的数组,存储的对象还是指向了旧的Vector存储的那些对象。也就是Vector这个类实现了深拷贝,但是对于存储的对象还是浅拷贝。

public synchronized Object clone() {
    try {
        @SuppressWarnings("unchecked")
        Vector<E> v = (Vector<E>) super.clone();
        v.elementData = Arrays.copyOf(elementData, elementCount);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}
10.与ArrayList比较

Vector与ArrayList底层是数组结构,元素可重复,有序(存储顺序),支持下标索引访问,允许null元素。

  • Vector 是同步的,因此效率低;ArrayList 线程不安全,效率高。
  • Vector 每次扩容请求其大小的 2 倍(也可以通过构造函数设置增长的容量); ArrayList 是 1.5 倍。
  • Vector序列话整个elementData数组;ArrayList只序列化elementData数组中保存元素的那部分。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值