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
相關文章
相關標籤/搜索