ArrayList 擴容機制分析

huangxy發表於2020-03-16

ArrayList 初始容量

ArrayList 有多個不同的構造方法,不同的構造方法的初始容量是不同的。介紹之前先看下 ArrayList 都有哪些變數

// 預設初始化容量=10
private static final int DEFAULT_CAPACITY = 10;
// 空陣列,當初始化容量為0時返回該陣列
private static final Object[] EMPTY_ELEMENTDATA = {};
// 空陣列,用於跟 EMPTY_ELEMENTDATA 區分開來,當使用預設構造方法建立的時候返回該陣列
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 元素存放的陣列
transient Object[] elementData;
// 元素個數(即list長度)
private int size;
// 記錄陣列被修改的次數
protected transient int modCount = 0;
// 陣列最大長度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
複製程式碼
  1. 無參構造方法,使用預設容量(預設容量為 10),並且設定 elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA

  2. 傳入初始容量(initialCapacity),判斷傳入容量的值。值 > 0,則 new 一個長度為 initialCapacity 的 Object 陣列;值 < 0,直接設定 elementData = EMPTY_ELEMENTDATA

  3. 傳入一個 Collection,如果 Collection 的長度為 0,同樣的,設定 elementData = EMPTY_ELEMENTDATA;否則,呼叫 CollectiontoArray 方法建立一個陣列,並複製給 elementData

/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/

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

/**
* Constructs an empty list with an initial capacity of ten.
*/

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

/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/

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 相關定義

ArrayList 有兩個概念,一個是 capacity,它表示“容量”,表示的是 elementData 的長度。另一個是 size,表示的是陣列中存放元素的個數,這個很好理解,我們經常呼叫 ArrayListsize() 方法返回的就是這個 size

ArrayList 如何實現擴容的?

以無參構造方法為例,初始容量為 10,當呼叫 add(E e) 方法往 ArrayList 中插入資料的時候,才真正分配容量,即呼叫 Add(E e) 方法新增第一個元素時,陣列擴容為 10

add(E e) 方法為入口,分析原始碼

/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/

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

每次呼叫 add(E e) 方法,都會呼叫 ensureCapacityInternal 方法,呼叫完 ensureCapacityInternal 方法後,將元素新增到 elementData 陣列的尾部,進入到 ensureCapacityInternal 方法

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);
}
複製程式碼

add 第一個元素的時候,minCapacity 為 1 ,因為是新增第一個元素,此時 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 結果為true,通過 Math.max 方法計算出 minCapacity 為 10

接下來看 ensureExplicitCapacity 方法,方法判斷 minCapacity 是否大於陣列長度,大於的話,表示陣列需要擴容,不擴容的話直接往陣列新增元素會導致陣列越界異常,掉呼叫 grow() 方法進行陣列的擴容

/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/

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);
}
複製程式碼

預設情況下,新建容量(newCapacity)會是原來容量(oldCapacity)的 1.5 倍(這裡使用了 >> 位運算子提高效率),一般情況下,如果擴容 1.5 倍後就大於最小容量(minCapacity),則使用這個 1.5 倍的容量,如果小於最小容量,就返回最小容量的值

當新建容量(newCapacity)大於最大陣列長度(MAX_ARRAY_SIZE)的時候,呼叫 hugeCapacity 方法

private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
複製程式碼

該方法很簡單,當 minCapacity < 0 的時候,丟擲記憶體溢位異常,否則返回最大陣列長度

最後,呼叫 Arrays.copyOf 方法新建一個原陣列的拷貝,並修改原陣列指向新建立的陣列。原陣列會被垃圾回收器回收

擴容總結

一般情況下,ArrayList 在呼叫 add 方法的時候,如果 size 長度不夠,則會進行擴容操作,將陣列長度擴容到原來的 1.5 倍。如果頻繁對 ArrayList 新增元素,則會頻繁執行擴容操作影響效能

為避免頻繁擴容造成效能下降。當知道將要存入陣列的元素數量的時候,可以呼叫 ArrayList(int initialCapacity) 構造方法顯示指定 ArrayList 的初始容量;或者可以通過手動呼叫 ensureExplicitCapacity(int minCapacity) 方法指定擴容長度,降低預設擴容頻率

ArrayList 有縮容嗎?

ArrayList 沒有縮容。無論是 remove 方法還是 clear 方法,它們都不會改變現有陣列 elementData 的長度。但是它們都會把相應位置的元素設定為 null,以便垃圾收集器回收掉不使用的元素,節省記憶體

掃碼關注我
一起學習,一起進步
微信搜尋"簧笑語"公眾號
微信搜尋"簧笑語"公眾號

相關文章