ArrayList
- 特點:按新增順序排列、可重複、非執行緒安全;
- 底層實現:陣列
- 擴容原理:初始化集合時,預設容量為 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;
}
新增元素-擴容
新增方法:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
size:ArrayList 的長度,可用 List#size() 獲取
新增方法一共做了兩件事:
- 判斷陣列容量是否足夠,不夠則給陣列擴容;
- 向陣列中新增指定的元素;
新增元素不用多說,向陣列中的下一個位置插入即可。下面著重介紹容量判斷的邏輯:
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.5 倍;(此處通過位運算計算,右移1位相當於除以 2,再加原來值即擴大1.5倍)
- 如果新容量小於所需最小容量,則將新容量賦值為所需最小容量;(這裡主要執行場景為集合初始化時,舊容量為0,新容量擴大1.5倍後也為0,這時新容量將被賦值為預設容量 10)
- 如果新容量超過最大陣列大小(int 最大值 - 8),這時會報記憶體溢位 或擴容為 int 的最大取值。
- 最後將原來陣列資料複製到擴容到新容量大小的陣列中。
新增元素-實際流程
首先初始化一個 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 的陣列。下面看第一次新增元素的流程:
1.進入新增方法 add(E e)
2.判斷容量大小
3.計算所需最小容量
當前陣列為空陣列,所以if 條件成立,DEFAULT_CAPACITY = 10,minCapacity = 1;
當前方法返回 10;
4.判斷陣列是否擴容
elementData 當前為空陣列,length = 0;minCapacity 為上一步返回的 10;所以此處會呼叫擴容方法 grow()
5.陣列擴容(初始化陣列)
這裡會根據上面指定的預設容量 10 來給陣列擴容。
- oldCapacity = 0;
- newCapacity = 0;
- newCapacity = 10;
- elementData 擴容為容量為10 的新陣列
6.新增元素
- elementData[0] = items
- size++ = 1;
陣列擴容
根據之前程式的執行,集合儲存資料的陣列在第一次新增元素時擴充容量為 10,所以在第 11 次新增元素時就會呼叫擴容的邏輯。
1、add() 方法
minCapacity = size + 1 = 11;
2、判斷容量大小
3、計算所需最小容量
非第一次 直接返回
4、判斷是否擴容
所需最小容量大於陣列長度,呼叫擴容方法。
5、擴容
- oldCapatity = 10;
- newCapatity = 15;
- 拷貝陣列內容到一個 容量為 15 的陣列中,完成擴容
6、新增元素
- elementData[10] = e;
- size ++ = 11;