1. 程式人生 > 實用技巧 >探索Vector的底層實現

探索Vector的底層實現

背景

上週釋出了探索ArrayList的底層實現,趁熱打鐵!實際上ArrayListVector的實現上非常相似,程式碼基本上都是一樣的,還是老樣子,先看註釋,我能說註釋都差不多一樣嗎。探索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)