java集合系列之Vector(源碼分析)

這篇文章開始介紹Vector。他和ArrayList有一些相似,其內(nèi)部都是通過一個容量能夠動態(tài)增長的數(shù)組來實現(xiàn)的。不同點是Vector是線程安全的。因為其內(nèi)部有很多同步代碼快來保證線程安全。為此,這篇文章,也會通過從源碼的角度來分析一下Vector,并和ArrayList等其他集合容器進行一個對比分析。

OK,開始今天的文章。

一、認識Vector

Vector可以實現(xiàn)可增長的對象數(shù)組。與數(shù)組一樣,它包含可以使用整數(shù)索引進行訪問的組件。不過,Vector的大小是可以增加或者減小的,以便適應(yīng)創(chuàng)建Vector后進行添加或者刪除操作。

為此我們先看一下Vector在整個java集合體系中的位置

1-java集合框架.jpg

從上面這張圖我們也會發(fā)現(xiàn)Vector和ArrayList是出于一個等級上面的,繼承關(guān)系也和ArrayList一樣。不過從宏觀上只能看到在整個體系中的位置,現(xiàn)在我們從Vector來看看他的繼承關(guān)系。

2-Vector繼承關(guān)系.png

現(xiàn)在我們就根據(jù)這張圖來進行一個分析,Vector繼承于AbstractList,實現(xiàn)了List、RandomAccess、Cloneable、 Serializable等接口。

(1)Vector 繼承了AbstractList,實現(xiàn)了List接口。

(2)Vector實現(xiàn)了RandmoAccess接口,即提供了隨機訪問功能。

(3)Vector 實現(xiàn)了Cloneable接口,即實現(xiàn)克隆功能。

(4)Vector 實現(xiàn)Serializable接口,表示支持序列化。

Vector實現(xiàn)的這些接口,表示會有這樣的能力。但是還有一點,就是Vector是線程安全的。下面我們從源碼的角度來分析一下Vector是如何實現(xiàn)這些接口和保持線程安全的特性的。

二、源碼分析Vector

(1)構(gòu)造方法

Vector的構(gòu)造方法一共有四個,因為四個都比較重要,所以在這里就給出四個

第一個: 創(chuàng)建一個空的Vector,并且指定了Vector的初始容量為10

public Vector() {
    this(10);
}

第二個:創(chuàng)建一個空的Vector,并且指定了Vector的初始容量

public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}

第三個:創(chuàng)建一個空的Vector,并且指定了Vector的初始容量和擴容時的增長系數(shù)

//initialCapacity:初始容量
//capacityIncrement:擴容時的增長系數(shù)
public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    //這句話表明,其底層就是通過數(shù)組來建立的
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
}

第四個:根據(jù)其他集合來創(chuàng)建一個非空的Vector

public Vector(Collection<? extends E> c) {
    elementData = c.toArray();
    elementCount = elementData.length;
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}

第四個需要解釋一下,首先是把其他集合轉(zhuǎn)化為數(shù)組,然后復(fù)制粘貼到Vector里面。

(2)增加元素

增加元素有兩個主要的方法,第一個是在Vector尾部追加,第二個是在指定位置插入元素。

第一個:在Vector尾部追加元素

public synchronized boolean add(E e) {
    modCount++;
    //判斷容量大?。喝裟苎b下就直接放進來,裝不下那就擴容
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}

我們再進來看一下ensureCapacityHelper(elementCount + 1)是如何實現(xiàn)的。

private void ensureCapacityHelper(int minCapacity) {
    //追加一個元素后,當前的容量是minCapacity
    // 若minCapacity > elementData原始的容量,則要按照minCapacity進行擴容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

現(xiàn)在相當于真正擴容的方法是grow方法,別著急我們再進來看看。

private void grow(int minCapacity) {
    //第一步:獲取elementData的原始容量
    int oldCapacity = elementData.length;
    //第二步:apacityIncrement:表示需要新增加的數(shù)量,如果大于0,那就擴充這么多,如果不大于0,那就翻倍
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                        capacityIncrement : oldCapacity);
    //第三步:若進行擴容后,capacity仍然小,則新容量改為實際需要minCapacity的大小
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //第四步:如果新數(shù)組的長度比虛擬機能夠提供給數(shù)組的最大存儲空間大,
    // 則將新數(shù)組長度更改為最大正數(shù):Integer.MAX_VALUE
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    //第五步:按照新的容量newCapacity創(chuàng)建一個新數(shù)組,然后再將原數(shù)組中的內(nèi)容copy到新數(shù)組中
    elementData = Arrays.copyOf(elementData, newCapacity);
}

在這一步我們擴容的時候首先就要排除一些異常的情況,首先就是capacityIncrement(需要增加的數(shù)量)是否大于0,如果大于0直接增加這么多。然后發(fā)現(xiàn)增加了上面那些還不夠那就擴充為實際需要minCapacity的大小。最后發(fā)現(xiàn)還不夠,就只能擴充到虛擬機能表示的數(shù)字最大值了。

第二個:在指定位置增加元素

這個就比較簡單了。我們直接看源碼就能看明白

public void add(int index, E element) {
    insertElementAt(element, index);
}
public synchronized void insertElementAt(E obj, int index) {
    //第一步:fail-fast機制
    modCount++;
    //第二步:判斷index下標的合法性
    if (index > elementCount) {
        throw new ArrayIndexOutOfBoundsException(index+ " > " + elementCount);
    }
    //第三步:判斷容量大小
    ensureCapacityHelper(elementCount + 1);
    //第四步:數(shù)組拷貝,將index到末尾的元素拷貝到index + 1到末尾的位置,將index的位置留出來
    System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
    elementData[index] = obj;
    elementCount++;
}

(3)刪除元素

刪除元素時候同樣也有兩種方法,第一個根據(jù)元素值來刪除,第二個根據(jù)下表來刪除元素。

第一個:根據(jù)元素值來刪除元素

public boolean remove(Object o) {
    return removeElement(o);
}

我們發(fā)現(xiàn)刪除元素其實是調(diào)用了removeElement()方法來刪除元素的,沒關(guān)系不要嫌麻煩,進入這個方法內(nèi)部看一下。

public synchronized boolean removeElement(Object obj) {
    //第一步:fail-fast機制
    modCount++;
    //第二步:查找元素obj在數(shù)組中的下標
    int i = indexOf(obj);
    //第三步:若下標不小于0:說明Vector容器中含有這個元素
    if (i >= 0) {
        //第四步:調(diào)用removeElementAt(int)方法刪除元素
        removeElementAt(i);
        return true;
    }
    return false;
}

到了這一步,我們又發(fā)現(xiàn),執(zhí)行刪除操作的還不是removeElement()方法,而是removeElementAt(i),我們再進入這個方法看看。

public synchronized void removeElementAt(int index) {
    //第一步:fail-fast機制
    modCount++;
    //第二步:index下標合法性檢驗
    if (index >= elementCount) {
        throw new ArrayIndexOutOfBoundsException(index + " >= " +elementCount);
    }
    else if (index < 0) {
        throw new ArrayIndexOutOfBoundsException(index);
    }
    //第三步:要移動的元素個數(shù)
    int j = elementCount - index - 1;
    if (j > 0) {
        //第四步:將index之后的元素向前移動一位
        System.arraycopy(elementData, index + 1, elementData, index, j);
    }
    elementCount--;
    elementData[elementCount] = null; 
}

到了這個方法我們其實可以分析一下,要刪除元素要移動大量的元素,時間效率肯定是不好的。畢竟Vector是通過數(shù)組來實現(xiàn)的,而不是通過鏈表。

第二個:刪除指定位置的元素

刪除指定位置的元素就比較簡單了,我們到指定的位置進行刪除就好了,但是同樣需要把后面的元素進行移位。

public synchronized E remove(int index) {
    //第一步:fail-fast機制
    modCount++;
    //第二步:index下標合法性檢驗
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);
    //獲取舊的元素值
    E oldValue = elementData(index);
    //第三步:計算需要移動的元素個數(shù)
    int numMoved = elementCount - index - 1;
    //第四步:將元素向前移動
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    elementData[--elementCount] = null; 
    return oldValue;
}

(3)更改元素

更改元素我們就先看一個吧。這個在大部分場景下一般不用(大部分,根據(jù)自己業(yè)務(wù)來定)。

public synchronized void setElementAt(E obj, int index) {
        if (index >= elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " +  elementCount);
        }
        elementData[index] = obj;
    }

(4)查找元素

查找元素我們給出三個,第一個查詢Vector容器中是否包含某個元素,第二個查詢第一次出現(xiàn)的指定元素的索引,第三個最后一次出現(xiàn)的指定元素的索引。

第一個:查詢Vector容器中是否包含某個元素

public boolean contains(Object o) {
    return indexOf(o, 0) >= 0;
}

我們發(fā)現(xiàn),查詢Vector是否包含某個元素時候,其實是調(diào)用了第二個方法,那我們直接就看第二個

第二個:查詢第一次出現(xiàn)的指定元素的索引

public synchronized int indexOf(Object o, int index) {
    //分為null和不為null
    if (o == null) {
        //從index處一個一個搜索
        for (int i = index ; i < elementCount ; i++)
            if (elementData[i]==null)
               return i;
    } else {
        for (int i = index ; i < elementCount ; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

第三個:查詢最后一次出現(xiàn)的指定元素的索引

public synchronized int lastIndexOf(Object o, int index) {
    if (index >= elementCount)
        throw new IndexOutOfBoundsException(index + " >= "+ elementCount);
    if (o == null) {
        //從index處正向搜索,默認從索引為elementCount-1處開始
       for (int i = index; i >= 0; i--)
           if (elementData[i]==null)
                return i;
    } else {
        for (int i = index; i >= 0; i--)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

小結(jié):

1)線程安全:

從上面的構(gòu)造方法還有增刪改查的操作其實我們都發(fā)現(xiàn)了,都有這么一個synchronized關(guān)鍵字,就是這個關(guān)鍵字為Vector容器提供了一個安全機制,保證了線程安全。

2)構(gòu)造方法:

Vector實際上是通過一個數(shù)組去保存數(shù)據(jù)的。當我們構(gòu)造Vecotr時;使用默認構(gòu)造函數(shù),默認容量大小是10。

3)增加元素:

當Vector容量不足以容納全部元素時,Vector的容量會增加。若容量增加系數(shù) 大于0,則將容量的值增加“容量增加系數(shù)”;否則,將容量大小增加一倍。

4)克隆:

Vector的克隆函數(shù),即是將全部元素克隆到一個數(shù)組中。

(5)遍歷

不過到這可還沒結(jié)束,還有重要的一點我們還沒說,那就是遍歷。其實在我之前的文章介紹ArrayList時候已經(jīng)提到過了,遍歷方式也就那么幾種,既然Vector是基于數(shù)組實現(xiàn)的,那么遍歷方式肯定也是隨機訪問最快。在這里代碼演示幾個:

//第一種
for (String string : t) {
    System.err.print(string);
}
//第二種
t.forEach(new Consumer<String>() {
    @Override
    public void accept(String t) {
        System.out.print(t);    
    }
});
//第三種:效率最高
for (int i = 0; i < t.size(); i++) {
    System.out.print(t.get(i)); 
}
//第四種
Iterator<String> it = t.iterator();
while (it.hasNext()) {
    String string = (String) it.next();
    System.err.print(string);
}
//第五種
Enumeration<String> enume = t.elements();
while(enume.hasMoreElements()){
     System.out.print(enume.nextElement().toString());
}

三、Vector與其他容器的區(qū)別

源碼看完了,對于Vector的實現(xiàn),我相信你也基本上明白其內(nèi)部實現(xiàn)了,下面就看看他和別的容器的區(qū)別,在文章一開始我們就提到了Vector其實基本上和ArrayList一樣的,下面對比分下一下:

ArrayList是線程非安全的,這很明顯,因為ArrayList中所有的方法都不是同步的,在并發(fā)下一定會出現(xiàn)線程安全問題。另一個方法就是Vector,它是ArrayList的線程安全版本,其實現(xiàn)90%和ArrayList都完全一樣,區(qū)別在于:

1、Vector是線程安全的,ArrayList是線程非安全的

2、Vector可以指定增長因子,如果該增長因子指定了,那么擴容的時候會每次新的數(shù)組大小會在原數(shù)組的大小基礎(chǔ)上加上增長因子;如果不指定增長因子,那么就給原數(shù)組大小*2,源代碼是這樣的:

int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);

OK,今天的文章就分享到這里,如有問題還請批評指正。

歡迎關(guān)注微信公眾號:java的架構(gòu)師技術(shù)棧,回復(fù)關(guān)鍵字可獲取計算機系列各種教程資源。包含java基礎(chǔ)、java進階、java工具、java框架、java架構(gòu)師、python、android、微信小程序、數(shù)據(jù)庫、前端、神經(jīng)網(wǎng)絡(luò)、機器學(xué)習(xí)等等各個方面的教程資源。


微信公眾號.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容