ArrayList 深入淺出

Ep流蘇發表於2021-07-23

ArrayList

  1. 特點:按新增順序排列、可重複、非執行緒安全;
  2. 底層實現:陣列
  3. 擴容原理:初始化集合時,預設容量為 0,第一次新增元素時擴容為 10,容量不夠時擴容為原來容量的 1.5 倍。

這裡擴容指的是無參構造初始化時的場景。對於指定集合長度的建構函式初始化時,初始容量為指定長度,容量不夠時再擴容為原來的 1.5 倍。

下面主要介紹無參構造初始化時的場景。

初始化-建構函式

引數定義:

transient Object[] elementData; // 實際儲存資料的陣列緩衝區

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 初始陣列

預設初始化非常簡單,呼叫無參構造器,初始化一個空陣列。真正擴容的邏輯在每次新增元素執行。

/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

image

新增元素-擴容

新增方法:

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

size:ArrayList 的長度,可用 List#size() 獲取

新增方法一共做了兩件事:

  1. 判斷陣列容量是否足夠,不夠則給陣列擴容;
  2. 向陣列中新增指定的元素;

新增元素不用多說,向陣列中的下一個位置插入即可。下面著重介紹容量判斷的邏輯:

1. 判斷容量大小

首先判斷當前集合容量大小是否足夠,如果不夠就呼叫擴容方法 grow(int minCapacity)。

// 確保集合可以新增下一個元素 minCapacity:當前需要的最小容量
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) {
    modCount++;

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

2. 擴容方法

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);
}
  1. 舊容量為當前陣列長度;
  2. 新容量為舊容量的 1.5 倍;(此處通過位運算計算,右移1位相當於除以 2,再加原來值即擴大1.5倍)
  3. 如果新容量小於所需最小容量,則將新容量賦值為所需最小容量;(這裡主要執行場景為集合初始化時,舊容量為0,新容量擴大1.5倍後也為0,這時新容量將被賦值為預設容量 10)
  4. 如果新容量超過最大陣列大小(int 最大值 - 8),這時會報記憶體溢位 或擴容為 int 的最大取值。
  5. 最後將原來陣列資料複製到擴容到新容量大小的陣列中。

新增元素-實際流程

首先初始化一個 ArrayList,向裡面迴圈新增 11 個元素。下面針對於第一次新增和第11次新增分別檢視 陣列初始化 和 陣列擴容的邏輯。

public static void main(String[] args) {
    List<string> list = new ArrayList<>();

    for (int i = 0; i < 11; i++) {
        list.add("items");
    }
}

陣列初始化

ArrayList 初始化後,底層會初始化一個空的物件陣列 (elementData),長度 (size) 為 0。當第一次新增元素時,會將其初始化為一個長度為 10 的陣列。下面看第一次新增元素的流程:
image

1.進入新增方法 add(E e)

image

2.判斷容量大小

image

3.計算所需最小容量

image

當前陣列為空陣列,所以if 條件成立,DEFAULT_CAPACITY = 10,minCapacity = 1;

當前方法返回 10;

4.判斷陣列是否擴容

image

elementData 當前為空陣列,length = 0;minCapacity 為上一步返回的 10;所以此處會呼叫擴容方法 grow()

5.陣列擴容(初始化陣列)

這裡會根據上面指定的預設容量 10 來給陣列擴容。

  • oldCapacity = 0;
  • newCapacity = 0;
  • newCapacity = 10;
  • elementData 擴容為容量為10 的新陣列

image

6.新增元素

  • elementData[0] = items
  • size++ = 1;

image

陣列擴容

根據之前程式的執行,集合儲存資料的陣列在第一次新增元素時擴充容量為 10,所以在第 11 次新增元素時就會呼叫擴容的邏輯。

1、add() 方法

minCapacity = size + 1 = 11;

image

2、判斷容量大小

image

3、計算所需最小容量

​ 非第一次 直接返回

image

4、判斷是否擴容

所需最小容量大於陣列長度,呼叫擴容方法。

image

5、擴容

  • oldCapatity = 10;
  • newCapatity = 15;
  • 拷貝陣列內容到一個 容量為 15 的陣列中,完成擴容

image

6、新增元素

  • elementData[10] = e;
  • size ++ = 11;
    image