Java 集合系列二、百密一疏之Vector

一、Vector 概述

在介绍Vector 的时候,人们常说:html

底层实现与 ArrayList 相似,不过Vector 是线程安全的,而ArrayList 不是。
复制代码

那么这句话定义的到底对不对呢?咱们接下来结合上一篇文章进行分析:java

Java 集合系列一、细思极恐之ArrayList面试

Vector 依赖关系

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

Vector 是一个矢量队列,它的依赖关系跟 ArrayList 是一致的,所以它具备一下功能:数组

  • 一、Serializable:支持对象实现序列化,虽然成员变量没有使用 transient 关键字修饰,Vector 仍是实现了 writeObject() 方法进行序列化。
  • 二、Cloneable:重写了 clone()方法,经过 Arrays.copyOf() 拷贝数组。
  • 三、RandomAccess:提供了随机访问功能,咱们能够经过元素的序号快速获取元素对象。
  • 四、AbstractList:继承了AbstractList ,说明它是一个列表,拥有相应的增,删,查,改等功能。
  • 五、List:留一个疑问,为何继承了 AbstractList 还须要 实现List 接口?

*拓展思考:为何 Vector 的序列化,只重写了 writeObject()方法?安全

细心的朋友若是在查看 vector 的源码后,能够发现,writeObject() 的注释中有这样的说法:bash

This method performs synchronization to ensure the consistency
    of the serialized data.
复制代码

看完注释,可能会有一种恍然大悟的感受,Vector 的核心思想不就是 线程安全吗?那么序列化过程确定也要加锁进行操做,才能过说其是线程安全啊。所以,即便没有 elementData 没有使用 transient 进行修饰,仍是须要重写writeObject()。多线程

*拓展思考:与ArrayLit,以及大部分集合类相同,为何继承了 AbstractList 还须要 实现List 接口?dom

有两种说法,你们能够参考一下:ide

一、在StackOverFlow 中:传送门 得票最高的答案的回答者说他问了当初写这段代码的 Josh Bloch,得知这就是一个写法错误。函数

二、Class类的getInterfaces 能够获取到实现的接口,却不能获取到父类实现接口,可是这种操做无心义。

二、Vector 成员变量

/**
        与 ArrayList 中一致,elementData 是用于存储数据的。
     */
    protected Object[] elementData;

    /**
     * The number of valid components in this {@code Vector} object.
      与ArrayList 中的size 同样,保存数据的个数
     */
    protected int elementCount;

    /**
     * 设置Vector 的增加系数,若是为空,默认每次扩容2倍。
     *
     * @serial
     */
    protected int capacityIncrement;
    
     // 数组最大值
     private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
复制代码

与ArrayList 中的成员变量相比,Vector 少了两个空数组对象: EMPTY_ELEMENTDATADEFAULTCAPACITY_EMPTY_ELEMENTDATA

所以,Vector 与 ArrayList 中的第一个不一样点就是,成员变量不一致

三、Vector 构造函数

Vector 提供了四种构造函数:

  • Vector():默认构造函数
  • Vector(int initialCapacity):capacity是Vector的默认容量大小。当因为增长数据致使容量增长时,每次容量会增长一倍。
  • Vector(int initialCapacity, int capacityIncrement):capacity是Vector的默认容量大小,capacityIncrement是每次Vector容量增长时的增量值。
  • Vector(Collection<? extends E> c):建立一个包含collection的Vector

乍一眼看上去,Vector 中提供的构造函数,与ArrayList 中的同样丰富。可是在上一节内容 中分析过 ArrayList 的构造函数后,再来看Vector 的构造函数,会以为有一种索然无味的感受。

//默认构造函数
    public Vector() {
        this(10);
    }
    
    //带初始容量构造函数
    public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
    
    //带初始容量和增加系数的构造函数
    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }

复制代码

代码看上去没有太多的问题,跟咱们平时写的代码同样,只是与ArrayList 中的构造函数相比 缺乏了一种韵味。有兴趣的同窗能够去看一下ArrayList 中的构造函数实现。

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);
    }
复制代码

JDK 1.2 以后提出了将Collection 转换成 Vector 的构造函数,实际操做就是经过Arrays.copyOf() 拷贝一份Collection 数组中的内容到Vector 对象中。这里会有可能抛出 NullPointerException

在构造函数上面的对比:Vector 的构造函数的设计上输于 ArrayList。

四、添加方法(Add)

Vector 在添加元素的方法上面,比ArrayList 中多了一个方法。Vector 支持的add 方法有:

  • add(E)
  • addElement(E)
  • add(int i , E element)
  • addAll(Collection<? extends E> c)
  • addAll(int index, Collection<? extends E> c)

4.1 addElement(E)

咱们看一下这个多出来的 addElement(E) 方法 有什么特殊之处:

/**
     * Adds the specified component to the end of this vector,
     * increasing its size by one. The capacity of this vector is
     * increased if its size becomes greater than its capacity.
     *
     * <p>This method is identical in functionality to the
     * {@link #add(Object) add(E)}
     * method (which is part of the {@link List} interface).
     *
     * @param   obj   the component to be added
     */
    public synchronized void addElement(E obj) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = obj;
    }
复制代码

从注释上面来看,这个方法就是 跟 add(E) 方法是有着同样的功能的。所以除了返回数据不一样外,也没什么特殊之处了。

咱们顺着上述代码来进行分析 Vector 中的添加方法。能够看到 Vector 对整个add 方法都上锁了(添加了 synchronized 修饰),其实咱们能够理解,在添加元素的过程主要包括如下几个操做:

  • ensureCapacityHelper():确认容器大小
  • grow():若是有须要,进行容器扩展
  • elementData[elementCount++] = obj:设值

为了不多线程状况下,在 ensureCapacityHelper 容量不须要拓展的状况下,其余线程恰好将数组填满了,这时候就会出现 ArrayIndexOutOfBoundsException ,所以对整个方法上锁,就能够避免这种状况出现。

与ArrayList 中对比,确认容器大小这一步骤中,少了 ArrayList#ensureCapacityInternal 这一步骤,主要也是源于 Vector 在构造时,以及建立好默认数组大小,不会出现数组为空的状况。

其次 grow() 方法中:

private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        //区别与ArrayList 中的位运算,这里支持自定义增加系数
        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);
    }
复制代码

Vector 中支持自定义的增加系数,也是它在 add() 方法中为数很少的亮点了。

4.2 add(int index, E element)

这部分代码跟ArrayList 中没有太多的差别,主要是抛出的异常有所不一样,ArrayList 中抛出的是IndexOutOfBoundsException。这里则是抛出 ArrayIndexOutOfBoundsException。至于为何须要将操做抽取到 insertElementAt() 这个方法中呢?童鞋们能够进行相关思考。

/**
     * @throws ArrayIndexOutOfBoundsException if the index is out of range
     *         ({@code index < 0 || index > size()})
     * @since 1.2
     */
    public 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++;
    }
    
复制代码

在添加方法上面,Vector 与ArrayList 大同小异。Vector 多了一个奇怪的 addElement(E)。

五、删除方法(Remove)

Vecotr 中提供了比较多的删除方法,可是只要查看一下源码,就能够发现其实大部分都是相同的方法。

  • remove(int location)
  • remove(Object object)
  • removeAll(Collection<?> collection)
  • removeAllElements()
  • removeElement(Object object)
  • removeElementAt(int location)
  • removeRange(int fromIndex, int toIndex)
  • clear()

5.一、remove(int location) & removeElementAt(int location)

对比一下 remove(int location)removeElementAt(int location)

public synchronized void removeElementAt(int index) {
        modCount++;
        if (index >= elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " +
                                                     elementCount);
        }
        else if (index < 0) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
        int j = elementCount - index - 1;
        if (j > 0) {
            System.arraycopy(elementData, index + 1, elementData, index, j);
        }
        elementCount--;
        elementData[elementCount] = null; /* to let gc do its work */
    }


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 是重写了父类的操做,而removeElement 则是Vector 中自定义的方法。ArrayList 中提供了 fastRemove() 方法,与其有着一样的效果,不过removeElement 做用范围为public。

5.二、remove(Object object) & removeElement(Object object)

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 object) 实际内部调用的就是 removeElement(Object object) 。删除操做首先找到 对象的索引(与ArrayList 中的remmove(E)同样),而后调用removeElementAt(i)(ArrayList 中调用 fastRemove()方法)进行删除。

其他删除操做与ArrayList 相似,这里不作详细解析。整体来讲,在删除方法这一块的话,Vector 与ArrayList 也是大同小异。

六、线程安全 Vector?

拓展思考,咱们常说Vector 是线程安全的数组列表,那么它究竟是不是无时无刻都是线程安全的呢?在StackOverFlow 中有这样一个问题:

StackOverFlow 传送门

Is there any danger, if im using one Vector(java.util.Vector) on my server program when im accessing it from multiple threads only for reading? (myvector .size() .get() ...) For writing im using synchronized methods. Thank you.

其中有一个答案解析的比较详细的:

Vector 中的每个独立方法都是线程安全的,由于它有着 synchronized 进行修饰。可是若是遇到一些比较复杂的操做,而且多个线程须要依靠 vector 进行相关的判断,那么这种时候就不是线程安全的了。

if (vector.size() > 0) {
    System.out.println(vector.get(0));
}
复制代码

如上述代码所示,Vector 判断完 size()>0 以后,另外一线程若是同时清空vector 对象,那么这时候就会出现异常。所以,在复合操做的状况下,Vector 并非线程安全的。

总结

本篇文章标题是:百密一疏之Vector,缘由在于,若是咱们没有详细去了解过Vector,或者在面试中,经常会认为Vector 是线程安全的。可是实际上 Vector 只是在每个单一方法操做上是线程安全的。

总结一下与ArrayList 之间的差别:

  • 一、构造函数,ArrayList 比Vector 稍有深度,Vector 默认数组长度为10,建立是设置。
  • 二、扩容方法 grow(),ArrayList 经过位运算进行扩容,而Vector 则经过增加系数(建立是设置,若是过为空,则增加一倍)
  • 三、Vector 方法调用是线程安全的。
  • 四、成员变量有所不一样

参考资料:

  • http://www.cnblogs.com/skywang12345/p/3308833.html
  • https://juejin.im/post/5aec1863518825671c0e6c75
  • https://stackoverflow.com/questions/23246059/java-vector-thread-safety?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
  • https://blog.csdn.net/ns_code/article/details/35793865
相关文章
相关标签/搜索