整體介紹
ArrayList實現了List接口,是一個常見的集合類,它有一下特點:
- 是順序容器,即元素存放的數據與放進去的順序相同,
- 允許放入null元素,
- 底層通過數組實現。
- 添加元素而容量不足時,可自動擴充底層數組的容量。
- 除該類未實現同步外,其余跟Vector大致相同。
- 操作時間復雜度分別為:
- size,isEmpty,get,set,iterator,listIterator方法運行時間為常量時間
- add方法運行為攤還常量時間,也即增加n個元素的時間為O(n)
- 其他的操作運行時間大致為線性時間,常數因子相較于LinkedList更小。
攤還時間是一個操作的平均代價,可能某次操作代價很高,但總體的來看也并非那么糟糕;add方法是攤還常量時間與底層數組容量自動擴充有關
image.png
源碼分析
成員變量
/**
* 初始容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 當ArrayList的空實例,用于無參數初始化
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* ArrayList的底層數組
* 泛型只是編譯器提供的語法糖所以這里的數組是一個Object數組
* ArrayList的容量就是elementData.length
* 當ArrayList為空時,elementData == EMPTY_ELEMENTDATA
* 當第一個元素加入時elementData.length == DEFAULT_CAPACITY
* transient 說明這個數組無法序列化。
*/
private transient Object[] elementData;
/**
* ArrayList包含的元素數量,與容量不同.
*/
private int size;
/**
* 數組最大容量
* 一些虛擬器需要在數組前加個頭標簽,所以減去8 。
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 這個變量并不是ArrayList里面定義的,而是繼承自父類AbstractList
* ArrayList在使用Iterator時發生不同步會拋出錯誤就是依靠了這個變量
(雖然不可靠,想要可靠的同步保證用
List list = Collections.synchronizedList(new ArrayList(...))
或
concurrent包下的CopyOnWriteArrayList
或
用synchronized進行一層代理)
* add和remove方法都是有modCount++.
* 下面再具體說說modCount的作用
*/
protected transient int modCount = 0;
get()
get()
方法本身很簡單,不涉及數組的容量擴充,除了檢查下標范圍以及類型轉換,與一般的數組get()
操作沒區別
public E get(int index) {
//檢查是否越界,
//由于java數組本身就會檢查是否越界,所以這里只是檢查是否超過了size,
//要知道數組的大小大于size,index大于size并不會觸發數組越界.
rangeCheck(index);
return elementData(index);
}
@SuppressWarnings("unchecked")
E elementData(int index) {
//對元素進行類型轉換,底層儲存是Object,但返回是E.
return (E) elementData[index];
}
set()
set()
方法也很簡單,不涉及數組的容量擴充,除了檢查下標范圍,與一般的數組set()
操作沒區別
public E set(int index, E element) {
//在get()中提到的,檢查越界問題.
rangeCheck(index);
//直接用element替換elementData[index]
//賦值到指定位置,復制的僅僅是引用
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
add()
如果不算入容量擴充的耗費,
add()
方法運行時間為常量時間.但是可能某次add()
觸發了容量擴充,那么那次操作的運行時間為線性時間.所以add()
的運行時間為攤還常量時間,即n次操作的時間為O(n).add()
會觸發modCount++
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/**
*從這個方法可知第一次add元素,初始容量為DEFAULT_CAPACITY,即10
*/
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
/**
*add()的modCount++來源于這里
*當新容量大于當前容量時,觸發容量擴充,即調用grow方法.
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
*ArrayList實現容量自動擴充是依靠了這個方法.
*新容量為原容量的1.5倍
*耗費時間為O(n),因為需要復制原來的元素到擴充后的數組.
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//每次擴充,新容量為原容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// //擴展空間并復制
elementData = Arrays.copyOf(elementData, newCapacity);
}
remove
-
remove
運行時間為O(n),因為ArrayList底層實現為數組,所以刪除元素后,需要移動之后的元素. - 除了越界檢查,與一般數組的
remove
沒有區別 - 在
remove
后并不會影響數組容量 -
remove
會觸發modCount++
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);
//這里清除了elementData[size]的引用,
//因為假如不清除引用,那么ArrayList就會一致保留那個對象
//GC不能清除仍被持有引用的對象
elementData[--size] = null;
return oldValue;
}
序列化
- 在上面成員變量那里提到了Object[] elementData用了transient關鍵字,無法序列化,這里ArrayList復寫了readObject和writeObject方法,實現底層數組的序列化.
- 復寫的writeObject用modCount保證了當發生不同步問題時拋出錯誤(雖然不可靠)
- 關于序列化的知識可以看我轉載的<<深入理解Java對象序列化>>
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// 記下方法開始時的modCount
int expectedModCount = modCount;
//將沒有transient關鍵字的成員變量寫入
s.defaultWriteObject();
//這里寫入數組的容量,但是把數組元素的數量size視為容量,
//而不用原來的容量elementData.length
s.writeInt(size);
// 將底層數組的元素依次寫入
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
//與之前記下的modCount對比,看是否有其他線程操作了ArrayList,有則拋出錯誤.
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// 將沒有transient關鍵字的成員變量讀入
s.defaultReadObject();
// 將數組容量讀入,
//但是如上面writeObject所寫把size視為了數組容量,所以這里沒有實質的作用
s.readInt(); // ignored
if (size > 0) {
// 擴充數組大小
ensureCapacityInternal(size);
Object[] a = elementData;
// 把數組元素一次寫入
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
迭代器
ArrayList用iterator()
可以返回迭代器.
- 實現了Iterator<E>接口;
private class Itr implements Iterator<E>
- 當有多個線程同時操作ArrayList,使用迭代器可以拋出錯誤,避免發生不同步(這里就是用了
modCount
,用法與writeObject()
的差不多) - 迭代器使得對容器的遍歷操作完全與其底層相隔離,可以到達極好的解耦效果。
iterator()
public Iterator<E> iterator() {
//使用構造函數,返回一個新的迭代器
return new Itr();
}
迭代器成員變量
int cursor; // 下一個返回的元素的下標
int lastRet = -1; // 上一次返回的元素的下標,初始為-1
//記下創建迭代器時的modCount,用于之后的同步檢查
int expectedModCount = modCount;
hasNext()
hasNext()
用于檢測是否有下一個元素返回,實現很簡單.
public boolean hasNext() {
return cursor != size;
}
checkForComodification()
- 這個方法是迭代器可以在多個線程操作時拋出錯誤(fail-fast機制)的關鍵方法
- 下面的
remove
和next
都調用了這個方法 - 實現很簡單,對比modCount,如果不一致,則說明有其他線程也在用ArrayList,拋出錯誤.
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
next()
-
next()
方法的實現很簡單,代碼寫的很清楚 -
next()
在內部做了很多安全檢查,保證了使用迭代器的安全性 -
next()
調用了checkForComodification()
,使用了fail-fast機制
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
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];
}
remove()
- 迭代器的
remove()
方法內部是調用了ArrayList的remove()
方法 -
remove()
調用了checkForComodification()
,使用了fail-fast機制
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();
}
}
后記
- ArrayList的源代碼寫的很好,注釋也很清晰,我在這篇分析里面有很多內容其實都是從注釋里翻譯,或者加了點自己的理解.
- 這里只是挑了幾個我覺得重要的地方做了分析,其余的推薦直接看源代碼