DEFAULT_CAPACITY
java
默認初始化容量,表明 elementData
數組的長度面試
EMPTY_ELEMENTDATA
數組
構造空 ArrayList
實例時,賦值給 elementData
的空數組實例app
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
函數
構造默認容量 ArrayList
實例時,賦值給 elementData
的空數組實例ui
elementData
this
ArrayList
實際存儲元素的動態數組spa
size
設計
ArrayList
中實際容納元素的個數3d
public ArrayList(int initialCapacity)
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//當賦值給 ArrayList 的容量爲 0 時,令 elementData = EMPTY_ELEMENTDATA;
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}
複製代碼
public ArrayList()
public ArrayList() {
//當構造默認容量的 ArrayList 時,令 elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
複製代碼
public ArrayList(Collection<? extends E> c)
public ArrayList(Collection<? extends E> c) {
// 將源集合的數據轉化爲 array 賦值給 elementData
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 {
this.elementData = EMPTY_ELEMENTDATA;
}
}
複製代碼
add(E e)
public boolean add(E e) {
// 保證添加元素後, ArrayList 不溢出
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
複製代碼
該方法調用了 ensureCapacityInternal
保證擴容後不會溢出。
ensureCapacityInternal(int minCapacity)
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
複製代碼
calculateCapacity(Object[] elementData, int minCapacity)
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
複製代碼
該方法會返回添加元素後最小的 capacity 的值。若是初始化 ArrayList
時未指定容量,那麼這個最小值將會是 DEFAULT_CAPACITY
。
ensureExplicitCapacity(int minCapacity)
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0) {
//若是添加元素後,數組的容量會大於現有數組的 length。須要將數組擴容
grow(minCapacity);
}
}
複製代碼
這裏維護了 modCount
的改變。
grow(int minCapacity)
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 1. 當 ArrayList 第一次放入 元素時,擴容後新的數組容量爲 1 或者 10;
// 2. 當 ArrayList 添加元素後容量大於 MAX_ARRAY_SIZE 時,容量會變爲 MAX_ARRAY_SIZE 或 Integer.MAX_VALUE
// 3. 其它狀況下,擴容操做使數組容量變爲原來的 1.5 倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
// 若新增元素後,oldCapacity * 1.5 > MAX_ARRAY_SIZE,調用 hugeCapacity 方法計算容量
if (newCapacity - MAX_ARRAY_SIZE > 0) {
newCapacity = hugeCapacity(minCapacity);
}
elementData = Arrays.copyOf(elementData, newCapacity);
}
複製代碼
這裏看到 ArrayList
擴容後新數組的容量有三種狀況。因此面試被問到 ArrayList
擴容後容量怎樣變化時,最好將三種狀況都說出來,而不僅是回答變爲原容量的 1.5 倍。
add(int index, E element)
public void add(int index, E element) {
// add 方法的索引範圍校驗
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
// 調用 System.arraycopy 將數組索引不小於 index 的元素後移
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
複製代碼
remove(int index)
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0) {
System.arraycopy(elementData, index + 1, elementData, index,
numMoved);
}
// 將最後一位元素置空,便於 GC 回收
elementData[--size] = null;
return oldValue;
}
複製代碼
remove(Object o)
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++) {
if (elementData[index] == null) {
//快速刪除,去除了索引越界檢驗以及返回值的保存
fastRemove(index);
return true;
}
}
} else {
for (int index = 0; index < size; index++) {
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
}
return false;
}
複製代碼
分爲空元素和非空元素兩種狀況遍歷。
刪除 ArrayList
中出現的第一個等於 o
的元素,查找到時,調用 fastRemove
方法刪除。
fastRemove
方法移除了索引越界檢查以及返回值的保存。
removeAll(Collection<?> c)
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
複製代碼
調用批量刪除方法 batchRemove
,注意這裏傳入的布爾類型參數值爲 false
。
batchRemove(Collection<?> c, boolean complement)
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
// r 表示 elementData 中待讀取的下一個數據對應的索引;w 表示待寫入的下一個索引
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++) {
// 若是是刪除集合中的元素,則將未出如今 c 中的元素寫入到索引爲 w 的位置;不然將出如今 c 中的元素寫入到索引爲 w 的位置。
if (c.contains(elementData[r]) == complement) {
elementData[w++] = elementData[r];
}
}
} finally {
// 保留與 AbstractCollection 行爲的兼容性
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// 將已寫入元素以後的元素置爲 null
for (int i = w; i < size; i++) {
elementData[i] = null;
}
// modCount 增長次數爲 刪除的元素個數
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
複製代碼
該方法設計的很巧妙。
設置了 r
和 w
兩個變量來控制數組的已讀標記和寫入標記。r
表示已經遍歷過的數據的索引,w
表示已寫入的數據的索引。(我的感受相似於 Netty
中 ByteBuf
的設計)
經過 complement
變量來控制寫入的數據是不是集合 c
中出現過的數據。若是 complement == true
,則寫入的元素爲該 ArrayList
實例 與集合 c
中共同存在的元素;若是 complement == false
,則寫入的元素爲該實例元素去除集合 c
中元素的結果。這裏是用來刪除集合 c
中的元素,因此傳入的變量值爲 false
。
在 finally
塊中,將 w
及以後的元素都置爲 null
。只留下符合要求的元素。同時,更新 modCount
的次數。
set(int index, E element)
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
複製代碼
修改操做很簡單,在數組賦值的基礎上增長了索引值校驗,注意該方法的返回值爲該索引位上的舊值。
get(int index)
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
複製代碼
查找操做同修改操做同樣,在數組查找操做上增長索引校驗。
clone()
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
複製代碼
這裏元素的拷貝調用了 Arrays.copyOf
方法,該方法是淺拷貝,因此 clone
方法呈現的結果也會是淺拷貝,對於拷貝出來的元素進行修改,會同時修改原 ArrayList
中的元素。
ArrayList
的方法都比較簡單,可是其中也有很多細節須要注意。
ArrayList
底層實現是動態數組,因此特色是元素的查找操做快,在末尾插入和刪除元素也很快,時間複雜度都是 O(1)。而在中間插入和刪除元素的均攤時間複雜度爲 O(n) 。
ArrayList
擴容時,其中的 elementData
容量變化會有三種狀況:
1. 當 `ArrayList` 第一次放入 元素時,擴容後新的數組容量爲 1 或者 10;
2. 當 `ArrayList` 添加元素後容量大於 `MAX_ARRAY_SIZE` 時,容量會變爲 `MAX_ARRAY_SIZE` 或 `Integer.MAX_VALUE`
3. 其它狀況下,擴容操做使數組容量變爲原來的 1.5 倍
複製代碼
ArrayList
的 clone
方法是淺拷貝。對拷貝出來的實例中的元素進行修改,會改變原來的 ArrayList
實例。