1. 程式人生 > 其它 >ArrayList擴容機制(基於jdk1.8)

ArrayList擴容機制(基於jdk1.8)

一.ArrayList繼承了AbstractList,實現了List介面,底層實現基於陣列,因此可以認為是一個可變長度的陣列。
二.在講擴容機制之前,我們需要了解一下ArrayList中最主要的幾個變數:

//定義一個空陣列以供使用
private static final Object[] EMPTY_ELEMENTDATA = {};
//也是一個空陣列,跟上邊的空陣列不同之處在於,這個是在預設構造器時返回的,擴容時需要用到這個作判斷,後面會講到
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存放陣列中的元素,注意此變數是transient修飾的,不參與序列化
transient Object[] elementData; //陣列的長度,此引數是陣列中實際的引數,區別於elementData.length,後邊會說到 private int size;

三.ArrayList有三個建構函式,不同的建構函式會影響後邊的擴容機制判斷
1.預設的無參構造

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

可以看到,呼叫此建構函式,返回了一個空的陣列DEFAULTCAPACITY_EMPTY_ELEMENTDATA,此陣列長度為0.
2.給定初始容量的建構函式

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

邏輯很簡單,就是構造一個具有指定長度的空陣列,當initialCapacity為0時,返回EMPTY_ELEMENTDATA
3.包含特定集合元素的建構函式

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

把傳入的集合轉換為陣列,然後通過Arrays.copyOf方法把集合中的元素拷貝到elementData中。同樣,若傳入的集合長度為0,返回EMPTY_ELEMENTDATA
四.擴容機制
擴容開始於集合新增元素方法,新增元素有兩種方法

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
public void add(int index, E element) {
    rangeCheckForAdd(index);

   ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,size - index);
    elementData[index] = element;
    size++;
}

可以看到兩個方法都呼叫了ensureCapacityInternal(size + 1)方法,把陣列長度加1以確保能存下下一個資料

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

此方法會先呼叫calculateCapacity方法,此時minCapacity為1,即size+1,因為初始時size為0

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

重點來了,此方法會判斷當前陣列是否為DEFAULTCAPACITY_EMPTY_ELEMENTDATA,之前就強調了無參構造時才會返回這個陣列。所以,若建立ArrayList時呼叫的是無參構造,此方法會返回DEFAULT_CAPACITY(值為10)和minCapacity的最大值,因此,最終會返回固定值10;若建立ArrayList時呼叫了有參構造,則此方法會返回1,注意這個minCapacity變數只是第一次呼叫add方法時值為1,此後的呼叫需要根據實際的陣列長度size+1。
然後呼叫ensureExplicitCapacity方法,

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

modCount++用到了快速失敗機制,此處先不做討論。
如果minCapacity大於elementData.length,則會呼叫grow方法,注意,這個elementData.length返回的是當前陣列的容量,而不是陣列實際的長度size。如果呼叫了有參構造,例如傳入的容量為5,則此時elementData.length值即為5,而此時第一次呼叫add時,size值為0,因此minCapacity為1,不滿足條件,此情況不需要擴容呼叫grow方法;如果呼叫了無參構造返回陣列DEFAULTCAPACITY_EMPTY_ELEMENTDATA,注意這個陣列只是一個空陣列,因此此時elementData.length為0,滿足條件,需要擴容呼叫grow方法。
可能說的太囉嗦,通俗來講,就是如果ArrayList給定了特定初始容量,則此處需要根據實際情況確定是否呼叫grow方法,即有可能不需要擴容。如果沒有指定初始容量,第一次呼叫add則此處一定需要呼叫grow方法。
那麼,下面就看grow方法都做了哪些處理吧

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    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);
}

int newCapacity = oldCapacity + (oldCapacity >> 1)此行程式碼即為擴容的核心,oldCapacity為原來的容量,右移一位,即除以2,因此這句的意思就是新的容量newCapacity=oldCapacity+oldCapacity /2,即原來的1.5倍。
然後判斷newCapacity如果小於傳入的minCapacity,則直接讓newCapacity等於minCapacity,即不需要擴容計算(當無參構造時,elementData.length為0,所以oldCapacity也為0,minCapacity為10,因此最終newCapacity為10)。
然後判斷newCapacity是否大於設定的MAX_ARRAY_SIZE,此處
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
如果大於,則呼叫hugeCapacity方法

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大於MAX_ARRAY_SIZE,則返回Integer的最大值,否則返回MAX_ARRAY_SIZE,最後,通過Arrays.copyOf方法把原陣列的內容放到更大容量的數組裡面。