天天看點

Java ArrayList vs. Vector相似點差別結束

ArrayList 和 Vector 的比較是面試中一個常見的問題,要想回答得好,還需要下點工夫,本文來總結下它們之間的相似以及差別。

相似點

先看下相似點。首先我們都是由數組來實作的,并且它們都實作了 RandomAccess 接口,是以支援快速随機通路,也就是通過索引快速定位元素。是以從本質上來說,它們之間是可以能通用的。

差別

Vector 現在很少用到,這是因為它與 ArrayList 之間的差別造成的。

線程安全

Vector 是線程安全的類,因為它的大部分方法都是通過 synchronized 關鍵字進行同步的,例如

add()

方法

public synchronized boolean add(E e) {
		// ...
        return true;
    }
           

然而 ArrayList 并不是線程安全的類,它的方法沒有 synchronized 關鍵字,也沒有通過鎖來保證線程安全。

大部分時候,我們都不需要考慮線程安全,是以我們經常使用 ArrayList 而不是 Vector ,畢竟同步是需要付出一定的性能代價的。然而如果一旦考慮線程安全問題,我們還是可以使用 Vector 的。

雖然 ArrayList 不是線程安全的,但是我們還是有辦法使之成為線程安全,例如我們可以在某個對象上進行同步,然後再操作 ArrayList 對象,例如下面的添加元素的操作

synchronized (object) {
	arraylist.add("element");
}
           

但是這似乎顯得比較笨拙,我們還可以在建立 ArrayList 對象時,使用

Collections.synchronizedList()

來建立一個 ArrayList 的包裝類對象

這樣是不是就友善很多。

ArrayList 還有一個線程安全的版本 CopyOnWriteArrayList,它的修改方法,例如

add()

,

set()

都是在底層數組的拷貝上進行的,但是 CopyOnWriteArrayList 通常用于周遊操作的數量遠遠大于修改的數量的情況下。

擴容方式

首先看下 ArrayList 的擴容方式

private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        // 擴容0.5倍大小
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
           

ArrayList 的基本擴容政策是先擴容0.5倍,然後再根據實際需要進行調整。

再來看下 Vector 擴容方式

private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        // 使用自定義擴容方式或者擴容1倍
        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);
    }
           

capacityIncrement

表示擴容時的增長量,這個是在構造函數時指派的,預設是0。

是以,Vector 的擴容政策是,如果指定的擴容的大小,那麼就使用自定義的政策,否則擴容一倍。再根據需要進行調整。

通常,我們并不知道設定擴容量為多大,才最合适,是以都不會在構造函數中設定這個值,那麼這個值預設就是0。是以 Vector 的基本擴容政策是擴容一倍。

我們并不能一概而論地說擴容政策誰優誰劣,要看實際情況而定的。

序列化

首先看下 ArrayList 的序列化

// tansient 修飾的數組,預設不會執行序列化操作
	transient Object[] elementData;
	
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        int expectedModCount = modCount;
        // 執行預設的序列化操作
        s.defaultWriteObject();
		
		// 儲存容量
        s.writeInt(size);
		
		// 儲存數組元素
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
           

ArrayList 背後的數組被

transient

關鍵字修飾,那麼它就不會執行預設序列化操作,是以在

writeObject()

方法中要手動進行儲存操作,為什麼這樣操作呢?

因為數組中可以存在一些空元素,如果使用預設序列化機制來儲存數組的元素,那麼這些空元素是會被儲存的。那麼在反序列化時,會額外為這些空元素配置設定不必須的空間。如此看來,ArrayList 的序列化操作還是比較高效的嘛。

我們再來看下 Vector 的序列化操作

protected Object[] elementData;
	
    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();
    }
           

首先我們注意到 Vector 背後的數組沒有使用

transient

關鍵字修飾,但是它還是定義了

writeObject()

方法來自己處理序列化操作。可以看到,Vector 序列化,儲存了數組的完整的克隆版本,其中就包括一些空元素。那麼反序列化時,可以會浪費額外的空間。

是以在序列化問題上,ArrayList 可能比 Vector 更高效點。

結束

在平時的工作中,Vector 很少用到,但是并不是無用武之地,隻是大部分時候我們不需要線程安全,是以 ArrayList 占據主流。并且在需要線程安全時,ArrayList 也提供了線程安全的方式,是以 Vector 漸漸退出曆史舞台。