ArrayList的擴容機制

初始容量

ArrayList有多個不一樣的構造函數,不一樣的構造函數的初始容量是不一樣的。快速看一下ArrayList源碼裏關於元素存放的幾個私有屬性:java

// 默認容量是10
private static final int DEFAULT_CAPACITY = 10;
// 若是容量爲0的時候,就返回這個數組
private static final Object[] EMPTY_ELEMENTDATA = {};
// 使用默認容量10時,返回這個數組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 元素存放的數組
transient Object[] elementData;
// 元素的個數
private int size;

// 記錄被修改的次數
protected transient int modCount = 0;
// 數組的最大值
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
複製代碼

ArrayList有三個構造方法,不一樣的構造方法的容量是不同的,具體能夠查看JDK 源碼。數組

  • 若是不傳入初始容量,就使用默認容量,並設置elementDataDEFAULTCAPACITY_EMPTY_ELEMENTDATA
  • 若是傳入初始容量,會判斷這個傳入的值,若是大於0,就new一個新的Object數組,若是等於0,就直接設置elementDataEMPTY_ELEMENTDATA
  • 若是傳入一個Collection,則會調用toArray()方法把它變成一個數組並賦值給elementData。一樣會判斷它的長度是否爲0,若是爲0,設置elementDataEMPTY_ELEMENTDATA

擴容具體指的是什麼?

能夠看到,ArrayList裏面有兩個概念,一個是capacity,它表示的就是「容量」,其實質是數組elementData的長度。而size則表示的「存放的元素的個數」。函數

由於Java中,數組操做不能越界,因此咱們必需要保證在插入操做的時候,不會拋出數組越界異常。性能

ArrayList是如何實現擴容的?

底層主要是這三個私有方法:spa

// 擴容一個
private Object[] grow() {
	return grow(size + 1);
}

// 保證擴容到指望容量minCapacity及以上
private Object[] grow(int minCapacity) {
    return elementData = Arrays.copyOf(elementData,
                                       newCapacity(minCapacity));
}

// 根據指望容量minCapacity計算實際須要擴容的容量
private int newCapacity(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length; // 獲得舊容量
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 設置新容量爲舊容量的1.5倍
    if (newCapacity - minCapacity <= 0) { // 若是新容量仍然小於指望容量
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // 若是是使用的默認容量
            return Math.max(DEFAULT_CAPACITY, minCapacity); // 取默認容量和指望容量較大值返回
        if (minCapacity < 0) // overflow // 檢查指望容量是否越界(int 的範圍)
            throw new OutOfMemoryError();
        return minCapacity; // 返回指望容量
    }
    // 若是新容量大於指望容量,判斷一下新容量是否越界
    return (newCapacity - MAX_ARRAY_SIZE <= 0)
        ? newCapacity
        : hugeCapacity(minCapacity);
}
複製代碼

能夠看到,底層實際上是調用了Arrays.copyOf方法來進行擴充數組容量的。這裏咱們主要看一下最後一個方法newCapacity(int minCapacity)的實現。code

默認狀況下,新的容量會是原容量的1.5倍,這裏用了位運算提升效率。通常狀況下,若是擴容1.5倍後就大於指望容量,那就返回這個1.5倍舊容量的值。而若是小於指望容量,那就返回指望容量。這裏對默認容量10作了特殊處理。內存

使用1.5倍這個數值而不是直接使用指望容量,是爲了防止頻繁擴容影響性能。試想若是每次add操做都要擴容一次,那性能將會很是低下。ci

上述grow方法其實主要是用於實現自動擴容的。而用戶也能夠經過調用如下方法實現手動擴容:element

public void ensureCapacity(int minCapacity) {
    if (minCapacity > elementData.length
        && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
             && minCapacity <= DEFAULT_CAPACITY)) {
        modCount++;
        grow(minCapacity);
    }
}
複製代碼

爲何須要手動擴容?試想一下,若是用戶已經知道即將存入大量的元素,好比目前有20個元素,即將存入2000個。那這個時候使用自動擴容就會擴容屢次。而手動擴容能夠一次性擴容到2000,能夠提升性能。rem

ArrayList有縮容嗎?

ArrayList沒有縮容。不管是remove方法仍是clear方法,它們都不會改變現有數組elementData的長度。可是它們都會把相應位置的元素設置爲null,以便垃圾收集器回收掉不使用的元素,節省內存。

相關文章
相關標籤/搜索