ArrayList 深入淺出

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