在上一篇博客 Java容器之ArrayList源碼分析(這應該是Java中最簡單的容器吧) 從源碼的角度分析了ArrayList
容器,現在我們看下Vector容器又是什麼。
註明:以下源碼分析都是基於jdk 1.8.0_221
版本
Java容器之Vector源碼分析目錄
一、Vector
容器概述
Vector
類的聲明如下:
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
相比於Vector
容器,大部分的小夥伴用的ArrayList
容器相對較多一點。既然Java已經設計了ArrayList
容器,爲啥要又引入Vector
容器,難道又是這些大佬太閒了?
看過我之前分析ArrayList
容器的源碼或者懂一點的肯定知道,ArrayList
容器沒有涉及任何鎖相關的內容,也就是說ArrayList
容器不支持併發讀寫,而這正是Vector
優勢所在。(不要說,爲了併發單獨設計出一個容器,有點小題大做。而是因爲鎖機制的引入會降低容器的讀寫能,在每次讀寫前都要上鎖、釋放鎖,增加了繁文縟節
。)
與ArrayList
容器一樣,Vector
容器的實現原理同樣是封裝了一個數組,並且數組的擴容、縮小都由容器自動控制。
二、Vector
類中主要屬性
Vector
類中主要屬性如下:
/**
* 容器底層封裝的存放數據的數組
*/
protected Object[] elementData;
/**
* 容器中包含的元素數量(注意與elementData數組長度的區別)
*/
protected int elementCount;
/**
* 當容器的大小大於其容量時,capacityIncrement爲容器自動遞增的量。
* 如果容量增量小於或等於零,則向量的容量將在每次需要增長時加倍。
*/
protected int capacityIncrement;
三、Vector
類的構造器
Vector
類一共有4個構造器,分別如下:
/**
* 指定容器初始化的容量、每次增長的量
*/
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
/**
* 指定容器初始化的大小,擴容增長的量設置爲0,表示容器每次擴大爲原來的2倍
*/
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
/**
* 默認容器初始化的大小爲10
*/
public Vector() {
this(10);
}
/**
* 複製構造器,將容器c中的元素copy到當前初始化的容器
*/
public Vector(Collection<? extends E> c) {
elementData = c.toArray();
elementCount = elementData.length;
// c.toArray()返回可能不是Object[].class的數組,需要轉換一下
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}
四、查找
相關方法
1、get
方法
/**
* 通過index獲取容器中的元素(此方法用雖不會修改容器,不過還是用synchronized修飾了,同步方法,this對象鎖,防止讀到不是最新的數據)
*/
public synchronized E get(int index) {
// 檢查是否超出最後一個元素的邊界(注意這裏的邊界檢查並不是檢查下標是否超過數組的長度)
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
// 下標合法則調用elementData方法獲取元素
return elementData(index);
}
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
2、contains
方法
/**
* 查找容器是否包含該對象
*/
public boolean contains(Object o) {
// 此方法雖沒有加鎖,但是在indexOf方法中加鎖了
return indexOf(o, 0) >= 0;
}
3、indexOf
方法
/**
* 查找對象在容器中的第一個下標
*/
public int indexOf(Object o) {
// 此方法雖沒有加鎖,但是在indexOf(o, 0)方法中加鎖了
return indexOf(o, 0);
}
/**
* 從[index, elementCount)搜索對象的第一個下標(此方法用synchronized修飾,this對象鎖)
*/
public synchronized int indexOf(Object o, int index) {
// 如果o是null,則不能調用equals方法,所以得分情況寫
if (o == null) {
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;
}
// 沒找就返回 -1
return -1;
}
還有一個lastIndexOf
方法,與indexOf
方法的作用類似,只不過是從後往前查找,找到的下標是最後一次出現的下標。
五、插入
相關方法
1、add
方法
/**
* 往容器尾端插入元素(修改操作,肯定要上鎖,使用synchronized修飾,this對象鎖
*/
public synchronized boolean add(E e) {
// 插入元素屬於結構性調整(該變量在父類AbstractList中有定義)
modCount++;
// 檢查容器是否還有空間(沒有就要擴容
ensureCapacityHelper(elementCount + 1);
// 插入尾端即可
elementData[elementCount++] = e;
return true;
}
/**
* 將元素插入到指定下標位置
*/
public void add(int index, E element) {
// 此方法雖沒有上鎖,但是insertElementAt方法上鎖了
insertElementAt(element, index);
}
/**
* 將元素插入到指定下標位置
*/
public synchronized void insertElementAt(E obj, int index) {
// 插入元素屬於結構性調整(該變量在父類AbstractList中有定義)
modCount++;
// 插入的下標必須在[0, elementCount)中
if (index > elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " > " + elementCount);
}
// 檢查容器是否還有空間(沒有就要擴容
ensureCapacityHelper(elementCount + 1);
// arraycopy的五個參數分別是,數組src,起始下標,數組des,起始下標,複製的個數
// 此時我們需要把elementData[index]這個位置空出來,也就是[index, elementCount)區間的元素全部往後移動一個位置,index移動到index+1,index+1移動到index+2
System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
elementData[index] = obj;
elementCount++;
}
2、set
方法
/**
* 修改容器對應下標中的元素(此方法同樣加鎖
*/
public synchronized E set(int index, E element) {
// 修改的下標必須在[0, index)區間內
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
六、刪除
相關方法
1、remove
方法
/**
* 刪除容器的指定對象
*/
public boolean remove(Object o) {
return removeElement(o);
}
/**
* 刪除容器的指定對象(只會刪除第一個,如果容器中包含多個),使用synchronized修飾,this對象鎖
*/
public synchronized boolean removeElement(Object obj) {
// 刪除元素屬於結構性調整(該變量在父類AbstractList中有定義)
modCount++;
// 獲取obj的第一個下標
int i = indexOf(obj);
if (i >= 0) {
// 查找到了則進行刪除
removeElementAt(i);
return true;
}
return false;
}
/**
* 通過下標移除元素
*/
public synchronized E remove(int index) {
// 刪除元素屬於結構性調整(該變量在父類AbstractList中有定義)
modCount++;
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
// [index + 1, elementCount)區間的中的元素全部前一個位置
int numMoved = elementCount - index - 1;
if (numMoved > 0)
// arraycopy的五個參數分別是,數組src,起始下標,數組des,起始下標,複製的個數
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--elementCount] = null; // Let gc do its work
return oldValue;
}
七、其它方法
1、trimToSize
方法
/**
* 此方法是去除容器尾端的空閒位置
*/
public synchronized void trimToSize() {
modCount++;
int oldCapacity = elementData.length;
if (elementCount < oldCapacity) {
// 只要把容器中的元素複製到一個大小剛好爲elementCount的數組即可
elementData = Arrays.copyOf(elementData, elementCount);
}
}
2、grow
擴容方法
/**
* 此方法雖沒有上鎖,但是調用此方法都是在插入無位置的時候,而插入的方法都加了鎖
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 前面在介紹Vector類屬性時,說過capacityIncrement是每次擴大的增長量,如果爲0,則每次擴大爲原來的2倍
int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
// 新的容量不能太小(小於minCapacity),也不能太大(大於Integet.MAX_VALUE
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 複製到一個長度爲newCapacity的新數組中即可
elementData = Arrays.copyOf(elementData, newCapacity);
}
八、總結
可以看出Vector
與ArrayList
容器的實現原理是一毛一樣的,都是封裝了一個數組,並且把數組的擴容、縮小交給容器自己管理。不過Vector
支持多線程併發訪問,因爲修改容器的方法都加上了synchronized
關鍵字修飾(this
對象鎖,鎖住整個容器對象)。由於底層是數組,所以隨機訪問(通過下標訪問)效率高,但是在刪除節點、插入節點時比較麻煩,刪除節點時需要進行大量元素的前移,插入節點時需要進行大量元素的後移,數組容量有限,還需要進行擴容(整個容器中的元素都要複製一遍),效率比較低。
總的來說就是Vector
、ArrayList
容器的查詢效率高,但是刪除、插入效率低。下一篇博文將分析LinkedList
容器,可以看看它與這兩個容器有什麼不同。