探索Vector的底層實現
背景
上週釋出了探索ArrayList的底層實現,趁熱打鐵!實際上ArrayList
與Vector
的實現上非常相似,程式碼基本上都是一樣的,還是老樣子,先看註釋,我能說註釋都差不多一樣嗎。探索Vector
原始碼是基於JDK1.8
的。
閱讀註釋
Vector內部是通過動態陣列實現的。
Vector可自定義擴容的大小,若沒有指定則預設翻倍,即2倍關係(100%),ArrayList預設是1.5倍關係(50%)
截圖中應該講的挺明白了!
又是快速失敗!
需要執行緒安全的情況下使用Vector,不需要則推薦使用ArrayList。
資料結構
//支援序列化、可克隆、隨機訪問 public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { //儲存元素的陣列緩衝區 //Vector的容量大小就是陣列緩衝區的長度大小 //Vector中排在最後一個元素後面的元素內容都是null,實際上指的是陣列長度過大,未存滿內容 protected Object[] elementData; //陣列中實際元素的個數,指的是包含多少元素,稱作有效元素 protected int elementCount; //當容量不足時,指定容量需要擴充的大小 //若該值小於或等於0,則當容量不足時採用翻倍的方式擴充大小 protected int capacityIncrement; //我們都知道定義一個數組的大小是 int 型別,那麼也就意味著最大的陣列大小應該是Integer.MAX_VALUE,但是這裡為啥要減去8呢? //查閱資源發現大部分的人都在說8個位元組是用來儲存陣列的大小,半信半疑 //分配最大陣列,某些VM會在陣列中儲存header word,按照上面的說法指的應該是陣列的大小 //若嘗試去分配更大的陣列可能會造成 OutOfMemoryError: 定義的陣列大小超過VM上限 //不同的作業系統對於不同的JDK可能分配的記憶體會有所差異,所以8這個數字可能是為了保險起見 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //Vector結構被修改的次數 //該欄位主要針對迭代器與子集使用,若該屬性被出乎意料的改變了,呼叫迭代器的相關方法,如 next、 remove、previous、set、add //則會丟擲 ConcurrentModificationException常,該情況其實就是上面提到的fail-fast //嚴格上來說,該欄位並不算是結構被修改的次數,在判斷是否需要擴容時,它是首先進行增加在判斷,不過這不影響,該欄位僅用來判斷是否與其他欄位相等 protected transient int modCount = 0; }
建構函式
/** * 構造一個指定初始容量大小與指定容量增長速率的空陣列 * 若initialCapacity小於0,則丟擲引數異常 * @param initialCapacity 初始容量大小 * @param capacityIncrement 當容量不足時,容量擴充的大小 */ public Vector(int initialCapacity, int capacityIncrement) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; this.capacityIncrement = capacityIncrement; } /** * 構造一個指定初始容量大小的空陣列 * 由於未指定容量增長速率,故而採用翻倍的機制 * @param initialCapacity 初始容量大小 */ public Vector(int initialCapacity) { this(initialCapacity, 0); } /** * 構造一個初始容量為10的空陣列 */ public Vector() { this(10); } /** * 構建一個包含指定collection集合的陣列,這些元素按照迭代器的順序排列 * collection集合型別有Map、set、List等子類,所以入參可以是多種型別 * collection集合轉換成陣列,elementData指向該陣列,elementCount成員屬性被賦值為collection集合長度 * 判斷陣列型別是否是Ojbect[],若不是則建立一個新的陣列,並拷貝elementData陣列中的內容 * @param c 指定集合 */ public Vector(Collection<? extends E> c) { elementData = c.toArray(); elementCount = elementData.length; if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, elementCount, Object[].class); }
-
若已經提前知道陣列容量,則建議使用new Vector(initialCapacity)
-
若不知道陣列容量的話,那就沒辦法了
-
Vector(Collection c)一般是在包含關係的情況下使用
方法說明
接下來按照類的宣告順序介紹方法
,有必要的情況下結合例子進行說明。
簡單方法
/** * 拷貝陣列中的有效元素到指定陣列中 * 指定陣列不能為null * 指定陣列的容量大小不能小於Vector中陣列的容量大小 * 指定陣列的型別必須能夠儲存Vector中陣列的元素型別 * @param anArray 指定陣列 */ public synchronized void copyInto(Object[] anArray) { System.arraycopy(elementData, 0, anArray, 0, elementCount); } /** * 縮小Vector的容量到當前陣列的大小,應用可以呼叫該方法來最小化Vector的儲存空間,簡單來說就是節約空間,去掉沒有用到的剩餘陣列空間 * elementCount 是指陣列的有效元素,實實在在的大小,而 elementData.length 是陣列的總容量大小,也就是說只有當填充/刪除元素時elementCount的大小才會變化 * 而當進行擴容時 elementData.length 才會變化,畢竟陣列的長度變大了 */ public synchronized void trimToSize() { modCount++; int oldCapacity = elementData.length; if (elementCount < oldCapacity) { elementData = Arrays.copyOf(elementData, elementCount); } } /** * 根據指定的容量增大或縮小Vector的容量大小 * 若newSize大於有效元素個數,增大的容量會使用null填充 * 若newSize小於有效元素個數,以newSize為起始索引到結尾的元素都被置為null導致元素丟失,容量保持不變 * 最後將 elementCount = newSize,導致在增大容量的情況下會出現null * * 若 elementData.length > newSize > elementCount, * 若 newSize > elementData.length,Vector容量擴大,多餘空間置null * 若 elementData.length > elementCount > newSize,則縮小Vector容量,導致元素丟失,容量保持不變 * 不管大小如何,Vector的有效元素個數都變成 newSize,可能出現null * @param newSize 指定容量大小 */ public synchronized void setSize(int newSize) { modCount++; if (newSize > elementCount) { ensureCapacityHelper(newSize); } else { for (int i = newSize ; i < elementCount ; i++) { elementData[i] = null; } } elementCount = newSize; } /** * 獲取陣列的容量大小 * @return 容量大小 */ public synchronized int capacity() { return elementData.length; } /** * 獲取陣列的有效元素個數 * @return 陣列有效元素個數 */ public synchronized int size() { return elementCount; } /** * 判斷陣列是否為空 * @return 陣列是否為空 */ public synchronized boolean isEmpty() { return elementCount == 0; } /** * 判斷陣列是否包含指定元素 * @return 是否包含指定元素 */ public boolean contains(Object o) { return indexOf(o, 0) >= 0; } /** * 採用正向遍歷的方式,獲取與指定元素相等的元素的索引 * 若存在多個元素,取第一次與指定元素相等的元素的索引 * 若返回-1則說明不存在指定元素 * @param o 指定元素 * @return 與指定元素的索引 */ public int indexOf(Object o) { return indexOf(o, 0); } /** * 採用正向遍歷的方式,根據指定起始索引開始,獲取與指定元素相等的元素的索引 * 若存在多個元素,取第一次與指定元素相等的元素的索引 * 若不存在則返回-1 * @param o 指定元素 * @param index 指定起始索引 * @return 與指定元素相等的元素的索引 */ public synchronized int indexOf(Object o, int index) { 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; } return -1; } /** * 採用反向遍歷的方式,獲取與指定元素相等的元素的索引 * 若存在多個元素,取第一次與指定元素相等的元素的索引 * 若不存在則返回-1 * @param o 指定元素 * @return 與指定元素相等的元素的索引 */ public synchronized int lastIndexOf(Object o) { return lastIndexOf(o, elementCount-1); } /** * 採用反向遍歷的方式,根據指定起始索引開始,獲取與指定元素相等的元素的索引 * 若存在多個元素,取第一次與指定元素相等的元素的索引 * 若不存在則返回-1 * @param o 指定元素 * @param index 指定起始索引 * @return 與指定元素相等的元素的索引 */ public synchronized int lastIndexOf(Object o, int index) { if (index >= elementCount) throw new IndexOutOfBoundsException(index + " >= "+ elementCount); if (o == null) { 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; } /** * 呼叫該clone之前,該類要實現Cloneable,不然會丟擲異常 * 陣列預設已經實現了Cloneable介面,直接呼叫方法即可,而且直接返回對應的型別,不需要向下轉型,同時包含陣列元素 * 淺拷貝與深拷貝,舉個例子吧 * 比如A類中包含基本型別與B類,當呼叫A類clone方法後,兩個A物件肯定是不一致,不然就不叫做拷貝了,不過這不是關鍵 * 若A1物件中的B物件與A2物件中的B物件指向同一個物件,則認為它是淺拷貝,認為B沒有被拷貝新的一份 * 若兩者指向不相等的話,則認為深拷貝,認為B重新拷貝了一份,不過這通常需要我們自定義程式碼,就像下面的方法一樣 */ public synchronized Object clone() { try { Vector<E> v = (Vector<E>) super.clone(); v.elementData = Arrays.copyOf(elementData, elementCount); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } } /** * 返回一個包含所有列表元素的有序(按照新增順序)陣列 * 此方法是建立一個新陣列,方便使用者能夠隨便操作新陣列 * @return 新陣列 */ public synchronized Object[] toArray() { return Arrays.copyOf(elementData, elementCount); } /** * 將列表的所有元素放入到指定陣列中並返回 * * 注意:T型別要麼是陣列中資料的相同型別,要麼是陣列中資料的父型別,運用多型性質 * 若傳入的新陣列容量 < 列表容量,則取它的類型別來建立一個包含列表元素的新陣列,並返回 * 若傳入的新陣列容量 > 列表容量,則將列表中的元素按照順序拷貝到新陣列中,同時將新陣列中索引為size的值設定成null * * 一開始我也好奇為啥要在索引為size上設定個null呢? * 看了註釋加上自我的理解,若傳入的新陣列是個空陣列的話,那麼除了拷貝列表元素後剩餘的所有空間的值都為null,此時在給索引為size的值設定成null似乎沒有多大 * 意思;另外一種情況是若傳入的新陣列不是個空陣列,那這個設定就有意義了,傳入的新陣列的某些元素會被列表元素覆蓋,同時有個null,剩下的才是自己本身的資料,呈現這樣子一種效果 * * List<Integer> list = new ArrayList<>(); * list.add(11); * * Integer[] str = new Integer[]{1,2,3,4,5,6,7,8,9,10}; * Integer[] s1 = list.toArray(str); * * for (Integer s : s1) { * System.out.println(s + ","); * } * * 輸出結果:11,null,3,4,5,6,7,8,9,10, * 那麼設定這個null的意義就在於能夠確定列表中元素個數(長度),目前我只有想到這一種情況下有用! * * @param a 指定陣列 * @return 填充完列表元素的指定陣列 */ public synchronized <T> T[] toArray(T[] a) { if (a.length < elementCount) return (T[]) Arrays.copyOf(elementData, elementCount, a.getClass()); System.arraycopy(elementData, 0, a, 0, elementCount); if (a.length > elementCount) a[elementCount] = null; return a; } /** * 獲取陣列中指定索引中的元素 * @param index 指定索引 * @return 指定索引的元素 */ E elementData(int index) { return (E) elementData[index]; } /** * 清空元素 */ public void clear() { removeAllElements(); } /** * 判斷陣列中是否包含指定集合中的所有元素 * 但凡集合中有一個元素不存在陣列中則返回false * @param c 指定集合 * @return 陣列中是否包含指定集合中的所有元素 */ public synchronized boolean containsAll(Collection<?> c) { return super.containsAll(c); } /** * 集合與陣列取交集 * 最終陣列中只包含與集合共有的元素,相當於在修改陣列 * @param c 指定集合 * @return 陣列元素是否被修改成功 */ public synchronized boolean retainAll(Collection<?> c) { return super.retainAll(c); } /** * 先判斷當前物件與指定物件是否指向同一個物件,就是在判斷地址 * 緊接著判斷指定物件屬於List的子類 * 緊接著獲取兩個物件的迭代器 * 若兩個迭代器的元素個數不相等,則返回false * 若兩個迭代器的元素個數相等,則將兩個迭代器的元素進行對應的比較,但凡出現對應的元素不相等則返回false * @param o 指定物件 * @return 當前物件與指定物件是否相等 */ public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof List)) return false; ListIterator<E> e1 = listIterator(); ListIterator<?> e2 = ((List<?>) o).listIterator(); while (e1.hasNext() && e2.hasNext()) { E o1 = e1.next(); Object o2 = e2.next(); if (!(o1==null ? o2==null : o1.equals(o2))) return false; } return !(e1.hasNext() || e2.hasNext()); } /** * 獲取雜湊值 * @return 雜湊值 */ public synchronized int hashCode() { return super.hashCode(); } /** * 獲取陣列元素的字串 * @return 陣列元素的字串 */ public synchronized String toString() { return super.toString(); } /** * 獲取指定起始索引到指定結束索引之間的元素,簡稱獲取指定子集 * 指定區間中的元素包括起始索引,不包括結束索引 * 若起始索引與結束索引相等,則返回空元素 * 對子集的操作,即呼叫set、add、remove等方法將會影響到整個陣列 * 但在先獲取子集後,又對整個陣列的結構進行修改,這時在遍歷子集則會導致報錯,而對於整體的非結構性修改則不會報錯,不過依然會影響到子集 * 所以在獲取子集後最好不要修改陣列的結構 * * 所有跟子集有關的方法和說明,可以參考ArrayList,基本上相似,除了在方法上加上Synchronized(this) * @param fromIndex 起始索引 * @param toIndex 結束索引 * @return 指定區間中的所有元素,稱為子集 */ public synchronized List<E> subList(int fromIndex, int toIndex) { return Collections.synchronizedList(super.subList(fromIndex, toIndex), this); } /** * 遍歷陣列,並對陣列中的元素進行指定處理 * 在遍歷過程中不允許修改結構,否則會丟擲錯誤 * @param action 函式式介面,對陣列中的元素指定處理 */ public synchronized void forEach(Consumer<? super E> action) { Objects.requireNonNull(action); final int expectedModCount = modCount; @SuppressWarnings("unchecked") final E[] elementData = (E[]) this.elementData; final int elementCount = this.elementCount; for (int i=0; modCount == expectedModCount && i < elementCount; i++) { action.accept(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } } /** * 根據指定條件移除元素 * 筆者對BitSet也是第一次接觸,針對本文章它顯的不是很重要,故而大概瞭解了一下 * * 該方法中將滿足條件的元素索引存放到BitSet中,同時記錄移除元素的個數removeCount * 緊接著BitSet呼叫 nextClearBit方法,該方法根據指定的索引獲取沒有在BitSet中存放的下一個索引,直接上個例子吧 * BitSet removeSet = new BitSet(); * removeSet.set(1) * removeSet.set(2) * System.out.println(removeSet.nextClearBit(1)) --> 3 * * 一開始已經在BitSet中存放了要移除的元素的索引,當呼叫nextClearBit方法迴圈遍歷獲取到的索引就是要保留的元素的索引 * 故而直接獲取元素的值將其存放到陣列中,最後的陣列是按照保留元素的順序進行存放的 * * 函式式介面中不能呼叫修改結構的方法 * @param filter 使用指定條件來過濾元素 * @return 是否移除成功 */ public synchronized boolean removeIf(Predicate<? super E> filter) { Objects.requireNonNull(filter); int removeCount = 0; final int size = elementCount; final BitSet removeSet = new BitSet(size); final int expectedModCount = modCount; for (int i=0; modCount == expectedModCount && i < size; i++) { @SuppressWarnings("unchecked") final E element = (E) elementData[i]; if (filter.test(element)) { removeSet.set(i); removeCount++; } } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } final boolean anyToRemove = removeCount > 0; if (anyToRemove) { final int newSize = size - removeCount; for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) { i = removeSet.nextClearBit(i); elementData[j] = elementData[i]; } for (int k=newSize; k < size; k++) { elementData[k] = null; // Let gc do its work } elementCount = newSize; if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } modCount++; } return anyToRemove; } /** * 根據指定規則替換所有舊元素 * operator.apply方法:舊元素作為入參傳入,根據規則返回新元素,然後進行替換 * operator.apply方法中不能呼叫修改結構的方法 * @param operator 指定規則,函式式介面 */ public synchronized void replaceAll(UnaryOperator<E> operator) { Objects.requireNonNull(operator); final int expectedModCount = modCount; final int size = elementCount; for (int i=0; modCount == expectedModCount && i < size; i++) { elementData[i] = operator.apply((E) elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } modCount++; } /** * 根據指定規則對陣列中的元素進行排序 * 若沒有指定規則則使用預設的升序進行排序 * 指定規則後會呼叫自定義比較器中的compare方法進行比較排序 * @param c 自定義比較器,覆寫compare方法 */ public synchronized void sort(Comparator<? super E> c) { final int expectedModCount = modCount; Arrays.sort((E[]) elementData, 0, elementCount, c); if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } modCount++; } /** * 獲取分割迭代器 * 由於該方法涉及到另外一個介面,會另外新起一篇文章來講解該內容,這裡就不做闡述 * 附上文章地址:http://zlia.tech/2019/08/28/explain-arraylist-spliterator-sourcecode * @return */ public Spliterator<E> spliterator() { return new VectorSpliterator<>(this, null, 0, -1, 0); }
自定義容量 + 擴容機制
/**
* 增加Vector的容量大小,在必要情況下,入參minCapacity至少要確保能容納元素的數量
* 若當前Vector的容量小於minCapacity,會通過替換內部陣列來增加容量大小,換句話就是建立更大長度的陣列,然後將elementData指向它
* 若capacityIncrement大於0,則新陣列的長度大小等於舊陣列長度大小 + capacityIncrement
* 若capacityIncrement小於或等於0,則新新陣列的長度大小等於舊陣列長度大小 * 2,也就是翻倍
* 但若新陣列的長度大小仍然小於minCapacity,則最終的陣列大小會是minCapacity
*
* 綜上所述:
* 擴容時,先計算自動擴充的容量大小
* 若capacityIncrement大於0,則自動擴容的容量大小是 舊陣列長度 + capacityIncrement
* 若capacityIncrement小於或等於0,則自動擴容的容量大小是 舊陣列長度 * 2
* 得到自動擴容的容量大小後,與minCapacity進行比較,取最大值作為新陣列的最終長度大小
* 最終將elementData指向新陣列即可
* @param minCapacity 手動擴充的容量大小
*/
public synchronized void ensureCapacity(int minCapacity) {
if (minCapacity > 0) {
modCount++;
ensureCapacityHelper(minCapacity);
}
}
/**
* 判斷是否需要擴容
* 注意該方法是沒有加synchronized關鍵字
* @param minCapacity 手動擴充的容量大小
*/
private void ensureCapacityHelper(int minCapacity) {
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* 首先獲取自動擴充的容量大小
* 若capacityIncrement大於0,則自動擴充的容量大小:舊陣列容量大小 + capacityIncrement
* 若capacityIncrement小於0,則自動擴充的容量大小:舊陣列容量大小 * 2
* 在不考慮capacityIncrement的前提下,兩次自動擴容的關係是翻倍,即2倍
*
* 判斷手動擴充的容量是否大於自動擴充的容量
* 若大於,則自動擴容的容量修改為手動擴充的容量,即 newCapacity = minCapacity,否則newCapacity不變,即採用自動擴充的容量
* 為了防止記憶體溢位,擴容並不是無止境的擴充,當大於一個臨界點MAX_ARRAY_SIZE時,就不允許在採用自動擴容的容量大小,而是取最大值或臨界點
*
* 參考hugeCapacity方法:
* 當程式執行到①時,我們可以知道 newCapacity >= minCapacity(指的是賦值之後的關係)
* 若MAX_ARRAY_SIZE大於newCapacity,則就開始建立長度為newCapacity的新陣列,三者的關係為 MAX_ARRAY_SIZE > newCapacity >= minCapacity
* 若MAX_ARRAY_SIZE小於newCapacity,則進入到hugeCapacity方法,但此時我們不知道minCapacity 與 MAX_ARRAY_SIZE的大小關係
* 若minCapacity大於MAX_ARRAY_SIZE,則採用最大值,不允許無限制的手動設定擴充容量,不過最大值有可能會出現記憶體溢位
* 三者關係為:newCapacity >= minCapacity > MAX_ARRAY_SIZE
* 若minCapacity小於MAX_ARRAY_SIZE,則採用臨界值,該臨界值是保證在不同的作業系統下不會發生記憶體溢位, 三者關係為:newCapacity > MAX_ARRAY_SIZE > minCapacity
* 得出結論:
* 新增元素時會先到底臨界值,此時不會發生記憶體溢位,若在往上增長則達到最大值,最大值有可能發生記憶體溢位
*
* @param minCapacity 手動擴充的容量大小
*/
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
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);
}
/**
* 判斷入參minCapacity 是否大於 MAX_ARRAY_SIZE
* 若 minCapacity > MAX_ARRAY_SIZE,則返回值是最大值
* 若 minCapacity <= MAX_ARRAY_SIZE,則返回值是臨界值
*
* @param minCapacity 手動擴充的容量大小
* @return 容量大小結果值
*/
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
自動擴容機制
- 先判斷capacityIncrement屬性,該屬性可以在建立Vector時設定,也可以使用預設值0
- 若capacityIncrement大於0,則每次自動擴容都以
capacityIncrement
大小進行增長 - 若capacityIncrement小於0,則每次自動擴容都以
2倍
的關係進行增長 - 如果期間手動擴充容量,則會比較手動擴充的容量大小與自動擴充的容量大小,取較大值進行擴容。
- 擴容是比較耗時的,應該盡力去避免,所以在初始化時就應該提供一個容量引數。
容量最大值
- 最大值是Interger.MAX_VALUE,但容易造成記憶體溢位,保險起見在容量等於Integer.MAX_VALUE - 8 的時候就應該停止擴充容量。
迭代器
將列舉類也歸到迭代器中。
/**
* 獲取包含所有有效元素的列舉類
* @return 列舉類
*/
public Enumeration<E> elements() {
return new Enumeration<E>() {
//下一個元素的索引
int count = 0;
/**
* 判斷當前列舉類中是否有下一個元素
* @return 是否有下一個元素
*/
public boolean hasMoreElements() {
return count < elementCount;
}
/**
* 獲取下一個元素
* 當獲取到下一個元素後,會發生count++ 以此來將索引進行移動,從而達到判斷是否有下一個元素
* 先呼叫hasMoreElements -> nextElement,否則會丟擲異常
* @return 下一個元素
*/
public E nextElement() {
synchronized (Vector.this) {
if (count < elementCount) {
return elementData(count++);
}
}
throw new NoSuchElementException("Vector Enumeration");
}
};
}
/**
* 返回一個包含指定索引到結尾之間的元素的列表迭代器
* 元素之間按照順序排序
* @param index 起始索引
* @return 包含元素的列表迭代器
*/
public synchronized ListIterator<E> listIterator(int index) {
if (index < 0 || index > elementCount)
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}
/**
* 返回一個包含所有元素的列表迭代器
* @return 包含元素的列表迭代器
*/
public synchronized ListIterator<E> listIterator() {
return new ListItr(0);
}
/**
* 獲取迭代器
* @return 迭代器
*/
public synchronized Iterator<E> iterator() {
return new Itr();
}
/**
* 迭代器,正向迭代
* 通過判斷是否存在下一個元素,若有則獲取,若沒有則說明迭代結束
* 由於這塊的程式碼與ArrayList中的程式碼是一模一樣,可以參考ArrayList,不多說了
* @param E 元素型別
*/
private class Itr implements Iterator<E> {
int cursor;
int lastRet = -1;
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != elementCount;
}
public E next() {
synchronized (Vector.this) {
checkForComodification();
int i = cursor;
if (i >= elementCount)
throw new NoSuchElementException();
cursor = i + 1;
return elementData(lastRet = i);
}
}
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
synchronized (Vector.this) {
checkForComodification();
Vector.this.remove(lastRet);
expectedModCount = modCount;
}
cursor = lastRet;
lastRet = -1;
}
@Override
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
synchronized (Vector.this) {
final int size = elementCount;
int i = cursor;
if (i >= size) {
return;
}
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) Vector.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
action.accept(elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
/**
* 列表迭代器,正向迭代
* 可獲取上一個元素、下一個元素及索引
* 與ArrayList一模一樣的程式碼,不多做說明了
*/
final class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
cursor = index;
}
public boolean hasPrevious() {
return cursor != 0;
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1;
}
public E previous() {
synchronized (Vector.this) {
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
cursor = i;
return elementData(lastRet = i);
}
}
public void set(E e) {
if (lastRet == -1)
throw new IllegalStateException();
synchronized (Vector.this) {
checkForComodification();
Vector.this.set(lastRet, e);
}
}
public void add(E e) {
int i = cursor;
synchronized (Vector.this) {
checkForComodification();
Vector.this.add(i, e);
expectedModCount = modCount;
}
cursor = i + 1;
lastRet = -1;
}
}
獲取元素
/**
* 獲取陣列中指定索引的元素
* @param index 指定索引
* @return 指定索引對應的元素
*/
public synchronized E elementAt(int index) {
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
}
return elementData(index);
}
/**
* 獲取陣列第一個元素
* @return 第一個元素
*/
public synchronized E firstElement() {
if (elementCount == 0) {
throw new NoSuchElementException();
}
return elementData(0);
}
/**
* 獲取陣列最後一個元素
* @return 最後一個元素
*/
public synchronized E lastElement() {
if (elementCount == 0) {
throw new NoSuchElementException();
}
return elementData(elementCount - 1);
}
/**
* 獲取陣列中指定索引的元素
* @param index 指定索引
* @return 指定索引對應的元素
*/
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
修改元素
/**
* 修改陣列中指定索引的元素
* @param index 索引
* @param element 新元素,替換索引對應的元素
*/
public synchronized void setElementAt(E obj, int index) {
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
elementData[index] = obj;
}
/**
* 修改陣列中指定索引的值
* 與上面的方法區別在於有無返回值
* @param index 索引
* @param element 新元素,替換索引對應的值
* @return 舊元素,索引對應的值
*/
public synchronized E set(int index, E element) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
移除元素
/**
* 移除陣列中指定索引的元素,移除元素之前會先進行角標越界判斷
* 移除過程中將index索引位置後續的所有元素都將向左移動一格
* 為了能讓GC儘可能地回收資源,主動將尾部位置設定成null
* @param index 指定索引
*/
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 */
}
/**
* 移除陣列中第一次出現的指定元素
* 若陣列中不存在指定元素則返回-1
* @param obj 指定元素
* @return 是否刪除成功
*/
public synchronized boolean removeElement(Object obj) {
modCount++;
int i = indexOf(obj);
if (i >= 0) {
removeElementAt(i);
return true;
}
return false;
}
/**
* 移除陣列中的所有元素
*/
public synchronized void removeAllElements() {
modCount++;
// Let gc do its work
for (int i = 0; i < elementCount; i++)
elementData[i] = null;
elementCount = 0;
}
/**
* 移除陣列中第一次出現的指定值
* 與上面的方法區別在於有無返回值
* @param o 指定元素
* @return 是否刪除成功
*/
public boolean remove(Object o) {
return removeElement(o);
}
/**
* 移除陣列中指定索引的值,移除元素之前會先進行角標越界判斷
* 移除過程中將index索引位置後續的所有元素都將向左移動一格
* 為了能讓GC儘可能地回收資源,主動將尾部位置設定成null
* @param index 索引
* @return 移除的舊值
*/
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;
}
/**
* 批量移除陣列中的指定陣列的元素
* @param c 指定移除的元素集合
* @return 是否移除成功
*/
public synchronized boolean removeAll(Collection<?> c) {
return super.removeAll(c);
}
/**
* 移除從指定起始索引到指定結束索引之間的所有元素
* 移除包含fromIndex索引對應的值,但不包括toIndex索引對應的值
* 移除過程中將toIndex索引位置及其後續的所有元素往左移動 toIndex - fromIndex 格
*
* 看到這裡的時候有些理解難題,在移除元素後索引位置上的元素主動設定成null,我明白這一點,不好理解的點在於演算法
* 假設如下:
* f t
* 1 2 3 4 5 6 7 8
*
* 移除3後的結果,注意4是不會被移除的:
* 1 2 6 7 8 9 null null
*
* 根據需求,我們知道要將8位置上的值設定成null,那麼問題就在於我怎麼才能知道7位置上的索引是多少呢?哦,是7,這個不算,演算法應該怎麼寫呢?
* 所以我很好奇怎麼是這個答案:size - (toIndex-fromIndex),後面著重理解了一下:
*
* f t
* 1 2 6 7 8 9 null null
* <= size =>
* <= t-f =>
* <= ? =>
* 求?的值,也就是在求null的索引是多少,看上面的圖就應該比較好理解了(不知道看的懂不),size - (toIndex-fromIndex)就剛好是索引的值
*
* @param fromIndex 起始索引
* @param toIndex 結束索引
*/
protected synchronized void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = elementCount - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// Let gc do its work
int newElementCount = elementCount - (toIndex-fromIndex);
while (elementCount != newElementCount)
elementData[--elementCount] = null;
}
/**
* 自定義反序列化
* 直接獲取指定鍵值
* @param in 輸入流
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gfields = in.readFields();
int count = gfields.get("elementCount", 0);
Object[] data = (Object[])gfields.get("elementData", null);
if (count < 0 || data == null || count > data.length) {
throw new StreamCorruptedException("Inconsistent vector internals");
}
elementCount = count;
elementData = data.clone();
}
/**
* 自定義序列化
* 直接設定指定鍵值並寫入流中
* @param s 輸出流
*/
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();
}
新增/插入元素
/**
* 新增元素到陣列中的指定位置,新增元素之前會先進行擴容和角標越界判斷
* 插入過程中將index索引位置及後續的所有元素都將向右移動一格,同時將當前索引位置的值修改成新值
* 陣列擴容跟size屬性沒有任何關係,size只負責陣列中有多少個元素,插入元素後故而 + 1
* @param obj 新元素
* @param 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++;
}
/**
* 新增元素到陣列尾部,新增元素之前會先進行擴容判斷
* @param obj 新元素
*/
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
/**
* 新增元素到陣列尾部,新增元素之前會先進行擴容判斷
* 與上面方法的區別在於是否有返回值
* @param e 新元素
* @return 是否新增成功
*/
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
/**
* 新增元素到陣列中的指定位置,新增元素之前會先進行擴容和角標越界判斷
* 插入過程中將index索引位置及後續的所有元素都將向右移動一格,同時將當前索引位置的值修改成新值
* @param index 索引
* @param element 新元素
*/
public void add(int index, E element) {
insertElementAt(element, index);
}
/**
* 新增陣列到另外一個數組中,從尾部開始追加
* 相當於合併兩個陣列
* @param c 陣列
* @return 陣列中的元素是否新增到另外一個數組中
*/
public synchronized boolean addAll(Collection<? extends E> c) {
modCount++;
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityHelper(elementCount + numNew);
System.arraycopy(a, 0, elementData, elementCount, numNew);
elementCount += numNew;
return numNew != 0;
}
/**
* 新增陣列到另外一個數組中,從指定索引出開始新增
* 插入過程中將index索引位置及後續的任何元素都將往右移動 numNew 格,相當於是批量插入
* 相當於在插入前先將原有的元素都往右移動,預先留出空位來給後面要新增的元素
* @param index 索引
* @param c 陣列
* @return 陣列中的元素是否新增到另外一個數組中
*/
public synchronized boolean addAll(int index, Collection<? extends E> c) {
modCount++;
if (index < 0 || index > elementCount)
throw new ArrayIndexOutOfBoundsException(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityHelper(elementCount + numNew);
int numMoved = elementCount - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
elementCount += numNew;
return numNew != 0;
}
總結
- Vector內部通過陣列實現,屬於執行緒安全,更準確地來說應該是相對執行緒安全。
/**
* 假設有兩個個執行緒分別呼叫下面的兩個方法
* 當執行緒1獲取到size方法返回的值時正要去執行get方法,但是卻被執行緒2搶先一步了,導致了remove方法先執行
* 等到remove方法執行完畢後,也就是刪除了最後一個元素,等到get方法執行時,最後一個元素實際上已經被刪除了,現在獲取的索引已經超出了範圍
* 故會丟擲索引越界錯誤,所以嚴格來說Vector屬於相對執行緒安全.
*/
public Object getLast(Vector list) {
return list.get(list.size() - 1); //執行緒1
}
public void deleteLast(Vector list) {
list.remove(list.size() - 1); //執行緒2
}
-
Vector每次自動擴充的容量大小支援自定義,通過傳入capacityIncrement即可。
-
建立空引數的Vector物件時,預設的初始容量是10,當容量不足時,以2倍速度增長。
-
構建Vector物件時,最好能預先設定容量大小,以免減少後期擴容花費的時間。
-
與迭代器不同,elements方法返回的列舉物件不會發生快速失敗。
-
Vector容量的臨界值是最大值 - 8,這個數字8是因為在陣列中除了儲存元素之外還會儲存陣列的長度,而這些資料都在記憶體中,不同作業系統對記憶體的分配可能有所差異,減去8更多的是為了防止記憶體溢位。
-
在獲取ArrayList的子集後不能在做結構上的修改。
-
獲取迭代器後,不允許進行結構修改操作,因為會 expectedModCount 與 modCount 是否相等。
-
在遍歷過程中不允許修改結構,否則會丟擲錯誤。
重點關注
預設每次自動擴容的關係是2倍
相對執行緒安全
預設初始容量是10
底層是通過陣列儲存元素,故是有序可重複集合
自動擴充的容量大小支援自定義(capacityIncrement)