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;