這篇文章開始介紹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集合體系中的位置

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

現(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
