JDK1.8--ArrayList

java中集合是重要的部分之一,在實際工作過程也被高頻率使用,這裏大概記錄一下1.8中的ArrayList,主要分析一下幾個常用的方法,擴容機制,以及多線程下引起的線程安全問題,純屬個人拙見,不足之處還請評論支出,共同學習進步!

1.對於集合整體架構如下圖所示:

 通過源碼我們可以看到ArrayLis的底層數據結構是數組,這也賦予了它檢索效率高的優勢,但是由於數組的侷限性,達到一定容量時需要擴容,爲了減少擴容帶來性能降低,我們在使用ArrayList初始化時可以根據使用場景預估其長度而定一個初始容量值,這樣可以減少頻繁擴容而帶來不必要的開銷。

2.常用方法

構造方法

通過註釋我們可以看出來三個構造方法適用於不同的場景,

/**
 * Constructs an empty list with the specified initial capacity.
 * 初始容量值的構造方法
 */
public ArrayList(int initialCapacity);

/**
 * Constructs an empty list with an initial capacity of ten.
 * 無參構造方法,初始容量爲10
 */
public ArrayList();

/**
 * Constructs a list containing the elements of the specified
 * collection, in the order they are returned by the collection's
 * iterator.
 * 構建一個包含具體集合元素的構造方法,它們的順序依次通過集合的迭代器返回
 */
public ArrayList(Collection<? extends E> c);

添加元素方法

  • 一個參數的add方法

/**
 * Appends the specified element to the end of this list.
 * 在list的末尾增加元素e
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

我們先來說說ArrayList的兩個重要參數,如註釋

/**
 * Shared empty array instance used for empty instances.
 * 用於空實例的空數組實例
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 * 用於默認大小空實例的空數組實例,我們將此與EMPTY_ELEMENTDATA區別開來,以瞭解添加第一個元素時需要擴容多少。
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

我們繼續跟進add方法

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity) {
    // 操作數記錄變量,Java快速報錯機制後續分析
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

calculateCapacity這個方法就利用瞭如上的兩個參數,如果是使用默認無參構造方法也就是DEFAULTCAPACITY_EMPTY_ELEMENTDATA數組對象時,增加第一個元素E e返回的是默認的容量長度DEFAULT_CAPACITY,在進入ensureExplicitCapacity方法後,經過if判斷後進行擴容,執行grow方法,我們繼續看grow方法

/**
 * Increases the capacity to ensure that it can hold at least the
 * number of elements specified by the minimum capacity argument.
 * 增加容量以確保它至少可以容納最小容量參數指定的元素數量。簡而言之就是擴容
 */
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // 右移1位操作,等效於oldCapacity/2,但是位移是底層操作更高效
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

對於擴容有幾種情況:

  • 如果使用默認構造方法,增加第一個元素時minCapacity = 10,經過grow方法後數組elementData從0擴容到10,後續再增加元素擴容時則是1.5倍的擴容
  • 如果使用初始值構造方法,但是初始容量爲0時,增加第一個元素minCapacity=1,經過grow方法後數組elementData從,0擴容到1,在第5次添加數據進行擴容的時候纔是按照當前容量的1.5倍進行擴容
  • 當擴容量(newCapacity)大於ArrayList數組定義的最大值後會調用hugeCapacity來進行判斷。如果minCapacity已經大於Integer的最大值(溢出爲負數)那麼拋出OutOfMemoryError異常

至此ArrayList擴容機制分析完畢,擴容完成後,添加新元素E e。

在ensureExplicitCapacity方法中有個一個增量modCount,這裏是利用了java的快速報錯機制,也算是一種保護機制,能夠防止多個進程同時修改同一個容器的內容。如果迭代遍歷比如使用迭代器Iterator(ListIterator)或者forEach,會將modCount變量值傳給exceptedModCount,並且會檢查modCount與exceptedModCount是否相等,如果此時對同一個容器執行新增刪除或者修改操作,modCount會改變,二者不相等則會拋出ConcurrentModificationException異常。但是如果使用Iterator的remove方法則不會發生此異常。舉例如下:

 

 剩下的三個添加元素的方法在此不再贅述。

此篇主要內容到此結束,有不足之處還請大家指正,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章