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

·  阅读 3570
Java 集合系列2、百密一疏之Vector

1、Vector 概述

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

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

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

Java 集合系列1、细思极恐之ArrayList

Vector 依赖关系

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

Vector 是一个矢量队列,它的依赖关系跟 ArrayList 是一致的,因此它具有一下功能:

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

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

细心的朋友如果在查看 vector 的源码后,可以发现,writeObject() 的注释中有这样的说法:

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

看完注释,可能会有一种恍然大悟的感觉,Vector 的核心思想不就是 线程安全吗?那么序列化过程肯定也要加锁进行操作,才能过说其是线程安全啊。因此,即使没有 elementData 没有使用 transient 进行修饰,还是需要重写writeObject()。

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

有两种说法,大家可以参考一下:

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

2、Class类的getInterfaces 可以获取到实现的接口,却不能获取到父类实现接口,但是这种操作无意义。

2、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 中的第一个不同点就是,成员变量不一致

3、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。

4、添加方法(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)。

5、删除方法(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.1、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.2、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 也是大同小异。

6、线程安全 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 之间的差异:

  • 1、构造函数,ArrayList 比Vector 稍有深度,Vector 默认数组长度为10,创建是设置。
  • 2、扩容方法 grow(),ArrayList 通过位运算进行扩容,而Vector 则通过增长系数(创建是设置,如果过为空,则增长一倍)
  • 3、Vector 方法调用是线程安全的。
  • 4、成员变量有所不同

参考资料:

  • http://www.cnblogs.com/skywang12345/p/3308833.html
  • https://juejin.cn/post/6844903601928667149
  • 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
分类:
后端
分类:
后端
收藏成功!
已添加到「」, 点击更改