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

peen發表於2018-05-05

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.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

相關文章