ArrayList擴容機制源碼分析

1、先看一下ArrayList的構造方法

1-1:空參構造方法:


private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

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

可以看到默認的空參構造方法是賦值了一個空數組

1-2:傳入默認大小/容量,構造方法

 private static final Object[] EMPTY_ELEMENTDATA = {};

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);
    }
}
  • 當你傳入的容量大於等於0,就會給你new出相對應大小的數組。不然就會拋出非法參數的異常
  • 因爲擴容是需要浪費時間的性能的。所以如果你開始就知道容量大小,應當直接選擇這種構造方法

1-2:傳入一個Collection的集合,構造方法

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;
     }
}

把你傳入的其它類型的集合,轉成ArrayList集合


2、看一下ArrayList是如何進入擴容的

看一下add()方法

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

可以看到首先就是調用了這個 ensureCapacityInternal,因爲你添加一個元素,可能會出現數組越界,那這個方法就是預防這個問題的。

我們再看一下這個 ensureCapacityInternal

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

這個裏面先調用了calculateCapacity,後調用 ensureExplicitCapacity,我們依次來看

private static final int DEFAULT_CAPACITY = 10;

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}
  • 這個方法就是判斷,目前這個集合/數組是否爲空,因爲我們看到使用空參構造出來的集合/數據是空的。如果是空的話。就取默認大小和當前大小的最大值。
  • 可以看到默認容量大小是 10

上面看了calculateCapacity,我們再來看下 ensureExplicitCapacity

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

這個 minCapacity 就是我們現在需要的容量大小,如果它大於當前的數組大小。那麼就觸發了 grow() 方法,也就是擴容方法。


3、現在我們可以來具體看一下這個 grow() 擴容方法了


private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

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);
}
簡單解釋一下 >> 右位移,你也可以直接記住是擴大了50%
    這裏直接把oldCapacity當作 999
    1、把 999 轉化成二進制 1111100111
    2、從右邊去掉一位變成 111110011 就是最後的結果,轉成十進制是 499
    3、你可以理解成 >> 後面接幾,就移除幾位
  • 通過上面的解釋我們知道了新的容量大小是之前的1.5倍。
  • 如果當前所需容量大小,還是大於新的容量大小。 就直接讓當前容量大小作爲擴容後的大小
  • 如果新的容量要是大於默認最大值,就需要調用一下hugeCapacity方法,取Integer和當前容量的最大值
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

4、總結

  • 當我們向集合裏面添加一個元素的時候,就會去判斷當前集合大小是否滿足。
  • 因爲使用無參構造方法創建的ArrayList,默認是空數組。所以需要先判斷數組是否爲空,如果爲空的話,就取當前長度和默認長度的最大值,默認長度是10。
  • 然後判斷當前所需空間是否大於數組的空間,如果不大於就不需要進行擴容了。如果大於則需要進去擴容。
  • 新的長度大小,是在舊的基礎上加上舊長度的0.5倍。使用向右位移一位計算出來的。
  • 如果新計算的長度還是小於當前需要的長度,就直接讓新的長度等於當前所需長度。
  • 最後得出的新長度要和最大的數組長度對比。如果大於最大長度。就需要讓新長度和最大長度比較,大於最大長度,就讓新長度等於Integer的最大值,否則就讓新長度等於數組最大長度。
  • 最後一步,得出了新的長度,調用Arrays.copyOf,生成新的數組。



感興趣的可以關注我的個人訂閱號:
在這裏插入圖片描述

發佈了313 篇原創文章 · 獲贊 139 · 訪問量 56萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章