ArrayList原始碼解析
1. 概述
ArrayList底層通過陣列實現,是執行緒不安全的,具有隨機訪問快(根據下標),隨機增刪慢的特點
ArrayList繼承AbstractList
抽象類,實現List
介面
2. 成員變數
/**
* 預設初始容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 用於空物件的共享空陣列
* 用於建立指定長度為0的ArrayList的構造方法
* java.util.ArrayList#ArrayList(int)
* java.util.ArrayList#ArrayList(java.util.Collection)
*
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 用於預設大小的空物件的共享空陣列(跟上面的區分開來)
* 用於建立預設長度為10的ArrayList的構造方法
* java.util.ArrayList#ArrayList()
*
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 儲存內容的陣列
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* ArrayList的長度
*/
private int size;
/**
* 陣列的最大長度
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
複製程式碼
這裡有一點要特別注意:
elementData.length
是指陣列的長度
size
是指List的長度
這兩個長度不是一樣的
3. 建構函式
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
// 初始容量大於0時,新建Object陣列指向elementData
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 初始容量等於0時,EMPTY_ELEMENTDATA陣列指向elementData
this.elementData = EMPTY_ELEMENTDATA;
} else {
// 初始容量小於0,丟擲異常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList() {
// 無參構造,elementData指向預設的空陣列
// 這也表明,elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的話
// 表示ArrayList還沒有新增過任何元素
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c) {
// collection轉陣列
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// 陣列長度不等於0
// [2] c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
// [1]
elementData = Arrays.copyOf(elementData,size,Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
複製程式碼
[1] 處ArrayList的原始碼中,涉及到陣列複製的,都是使用Arrays提供的工具類
[2] 處關於toArray方法不一定返回Object陣列,可以看看這篇文章 c.toArray might not return Object[]?
如果是涉及到陣列的移動(有新增、刪除引發),會使用
java.lang.System#arraycopy
方法
4. 常用的方法
4.1 新增
方法執行流程
可以看到add
和addAll
方法都會使用到ensureCapacityInternal
,該方法中的核心程式碼是對ensureExplicitCapacity
方法的呼叫
ensureExplicitCapacity
有兩點作用:
- 增加ArrayList的修改次數
modCount
- 容量不夠的情況下,執行
grow
方法,進行擴容操作
modCount
記錄了物件被修改的次數
java.util.ArrayList#add(E)
public boolean add(E e) {
/*
1. 確保elementData陣列擁有足夠的容量
2. 增加修改次數
*/
ensureCapacityInternal(size + 1); // Increments modCount!!
// 陣列新增元素
elementData[size++] = e;
return true;
}
/**
* 確保陣列擁有足夠的容量
* @param minCapacity 最小需要的容量
*/
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 表示elementDat還沒有新增過元素
minCapacity = Math.max(DEFAULT_CAPACITY,minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
// 增加修改次數
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
// 當最小需要的容量超出了陣列的長度,則需要擴容
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 擴容1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 擴容後還是小於需要的最小容量,則使用minCapacity作為newCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 擴容後發現newCapacity大於最大的陣列長度
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 通過Arrays工具類進行陣列擴容
// minCapacity is usually close to size,so this is a win:
elementData = Arrays.copyOf(elementData,newCapacity);
}
複製程式碼
java.util.ArrayList#add(int,E)
public void add(int index,E element) {
// 檢查index有沒有越界
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
// 將elementData[index:]的元素移動到elementData[index+1:]
System.arraycopy(elementData,index,elementData,index + 1,size - index);
// 在index位置新增元素
elementData[index] = element;
size++;
}
private void rangeCheckForAdd(int index) {
// [1]
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
複製程式碼
- [1]處判斷是否越界不是根據
elementData
的長度,而是根據ArrayList的長度size
去判斷
java.util.ArrayList#addAll(java.util.Collection<? extends E>)
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a,0,numNew);
size += numNew;
return numNew != 0;
}
複製程式碼
方法很簡單,自己look look就行了
4.2 刪除
java.util.ArrayList#remove(int)
public E remove(int index) {
// 檢查是否越界
rangeCheck(index);
modCount++;
// 根據index找到對應的value
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
// 將elementData[index+1:]移動到elementData[index:]
System.arraycopy(elementData,index+1,numMoved);
// 清空最後一個元素
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
E elementData(int index) {
return (E) elementData[index];
}
複製程式碼
java.util.ArrayList#remove(java.lang.Object)
public boolean remove(Object o) {
if (o == null) {
// 如果o為null物件
for (int index = 0; index < size; index++)
// 正序遍歷,將第一個null物件移除
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;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData,numMoved);
elementData[--size] = null; // clear to let GC do its work
}
複製程式碼
其他刪除的方法的實現都不負責,有興趣可以自己挑來看看
這裡可以看到,很多方法裡面,關於資料的移動,都是通過
System.arraycopy
實現
4.3 查詢
public E get(int index) {
// 檢查陣列越界
rangeCheck(index);
return elementData(index);
}
複製程式碼
方法很簡單,自己look look
5. 內部類
SubList
ArrayList有個subList
的方法,這個方法返回的是ArrayList的一個檢視
public List<E> subList(int fromIndex,int toIndex) {
subListRangeCheck(fromIndex,toIndex,size);
return new SubList(this,fromIndex,toIndex);
}
複製程式碼
方法返回的是SubList類的物件
可以看到這個類把ArrayList的增刪改查方法基本都自己實現了一遍
先來看看構造方法
SubList(AbstractList<E> parent,int offset,int fromIndex,int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
// [1]
this.modCount = ArrayList.this.modCount;
}
複製程式碼
可以看到[1]處,在建立SubList物件的時候,會把當時ArrayList.modCount的值賦值給SubList.modCount,Sublist的modCount的用處體現在checkForComodification
方法中(下文會提及)
再來看看增刪改查的流程
可以看到增刪改查的方法都會先呼叫檢查邊界的方法rangeCheck
、rangeCheckForAdd
,然後呼叫checkForComodification
checkForComodification
方法的作用是檢查SubList物件的modCount跟當前ArrayList的modCount是否相等
private void rangeCheck(int index) {
if (index < 0 || index >= this.size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private void rangeCheckForAdd(int index) {
if (index < 0 || index > this.size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private void checkForComodification() {
// [1]
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
複製程式碼
可以看到[1]處,當檢查modCount不相等的時候,表示這時還有其他物件在修改ArrayList,就會丟擲ConcurrentModificationException
modCount
在Itr
內部類中也起著相同的作用
現在來看看SubList增刪改查的具體是現實
public void add(int index,E e) {
rangeCheckForAdd(index);
checkForComodification();
// 實際呼叫的是ArrayList的add方法
parent.add(parentOffset + index,e);
// 同步一下modCount的資訊
this.modCount = parent.modCount;
this.size++;
}
public E remove(int index) {
rangeCheck(index);
checkForComodification();
// 實際呼叫的是ArrayList的remove方法
E result = parent.remove(parentOffset + index);
// 同步一下modCount
this.modCount = parent.modCount;
this.size--;
return result;
}
public E set(int index,E e) {
rangeCheck(index);
checkForComodification();
// 呼叫ArrayList的elementData方法
E oldValue = ArrayList.this.elementData(offset + index);
ArrayList.this.elementData[offset + index] = e;
return oldValue;
}
public E get(int index) {
rangeCheck(index);
checkForComodification();
// 呼叫ArrayList的elementData方法
return ArrayList.this.elementData(offset + index);
}
複製程式碼
正如上面所提及的,SubList對增刪改查的實現就是檢查邊界
+ 比較modCount
+ 呼叫ArrayList本身對應的方法
Itr
在ArrayList中會有iterator
方法,方法具體實現如下
public Iterator<E> iterator() {
return new Itr();
}
複製程式碼
可以看到方法是直接返回一個新建的Iter類物件
Itr內部類實現了Iterator介面,來看看它的成員變數
int cursor; // 下一個元素的下標
int lastRet = -1; // 最後一次返回的元素的下標; -1 if no such
int expectedModCount = modCount; // [1]期望的修改次數
複製程式碼
可以從[1]處看到,Itr物件也會使用modCount,實際作用也是檢查當前有沒有別的物件在修改ArrayList,如果有就拋ConcurrentModificationException
異常
接下來看看常用方法的實現
public boolean hasNext() {
return cursor != size;
}
public E next() {
// 檢查modCount
checkForComodification();
int i = cursor;
// 檢查有沒有超出size
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
// 檢查有沒有超出陣列長度
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
複製程式碼
方法還是比較容易讀懂的,可以自己看看
6. 總結
ArrayList是底層基於陣列實現的常用的集合類,相較於陣列,ArrayList內部實現了關於陣列越界、擴容相關的檢查,使用起來比資料要簡單,當然,陣列的效率肯定會比ArrayList要高