以下針對JDK 1.8版本中的ArrayList進行分析。
概述
ArrayList
基於List
接口實現的大小可變的數組。其實現了所有可選的List
操作,並且元素允許為任意類型,包括null
元素。除了實現List
接口,此類還提供了操作內部用於存儲列表數組大小的方法(這個類除了沒有實現同步外,功能基本與Vector
一致)。
每個ArrayList
實例都有一個容量。容量是用於存儲列表中元素的數組的大小。它始終至少與列表大小一樣大。隨着元素添加到ArrayList
,其容量會自動增加。除了添加元素具有恆定的攤銷時間成本這一事實之外,增長策略並沒有詳細指出。
我們在添加大容量數據的時候可以使用ensureCapacity
方法來主動擴容,這可以減少自動擴容的次數。
值得注意的是,這些實現都不是同步的。因此,當多個線程並發訪問一個ArrayList
實例並且至少有一個線程對這個實例進行結構性調整的時候,必須在外部額外實現同步(對於結構性調整,主要指增加或刪除一個或多個元素,更精確的說,就是對這個列表的大小進行了調整,對於更改元素的數值並非是結構性調整)。這通常通過在自然封裝列表的某個對象上進行同步來完成。如果不存在此類對象,則應使用Collections.synchronizedList
方法“包裝”該列表。這最好在創建時完成,以防止意外地不同步訪問列表:List list = Collections.synchronizedList(new ArrayList(...));
注意,迭代器的快速失敗行為無法得到保證,因為一般來說,不可能對是否出現不同步並發修改做出任何硬性保證。快速失敗迭代器會盡最大努力拋出ConcurrentModificationException
。因此,為提高這類迭代器的正確性而編寫一個依賴於此異常的程序是錯誤的做法:迭代器的快速失敗行為應該僅用於檢測 bug。
源碼分析
主要字段
/**
* 默認初始容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 用於ArrayList空實例的共享空數組實例
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 用於默認大小空實例的共享空數組實例。我們將this(DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
* 和EMPTY_ELEMENTDATA區別開來,以便在添加第一個元素時知道數組大小要擴容為多少多少。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 存儲 ArrayList 元素的數組緩沖區。ArrayList 的容量是此數組緩沖區的長度。
* 當第一個元素添加進空數組時候 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 將會被擴容至 DEFAULT_CAPACITY
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* ArrayList的數組大小(ArrayList包含的元素個數
*/
private int size;
注意此處的elementData
字段是用的transient
修飾的以及對於空實例有DEFAULTCAPACITY_EMPTY_ELEMENTDATA
和EMPTY_ELEMENTDATA
兩個共享空數組實例,下面會對提到的這些注意點進行分析。
構造函數
/**
* 根據指定的容量初始化空的列表,注意當容量為 0 時,使用的是 EMPTY_ELEMENTDATA
*/
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);
}
}
/**
* 初始化容量為 10 的空列表
*/
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.
*/
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;
}
}
注意到對於無參構造器使用的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,而對於帶參構造器,當 initialCapacity 為0時,使用的是EMPTY_ELEMENTDATA
;另外,在無參構造器中的注釋——“初始化容量為10的空列表”,我們不禁有以下疑惑:
EMPTY_ELEMENTDATA
和DEFAULTCAPACITY_EMPTY_ELEMENTDATA
都是空的對象數組,為什么在構造器中要對其進行區分- 無參構造器中,只是把空的對象數組賦值給了
elementData
,為什么注釋稱聲明了長度為10的空數組
對於以上問題,將在存儲和擴容部分進行講解。
存儲和擴容
/**
* 增加 ArrayList 實例的容量,確保 ArrayList 實例能存儲 minCapacity 個元素
*/
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
當ArrayList實例是個空列表並且 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
時,minExpand 設置為DEFAULT_CAPACITY
(10),此時如果 minCapacity 小於 minExpand,那么不馬上進行擴容操作,在進行add
操作時候,會初始化一個容量為 10 的空列表,這樣不僅符合無參構造器中的注釋,並且保證了 ArrayList 實例能夠存儲 minCapacity 個元素。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
在add
操作內部,會調用這個私有方法來確保有足夠的容量來放置元素。注意,這個函數一開始就對 elementData 進行判斷是否為 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,如果是的話,證明是無參構造器初始化的實例,在下一步會初始化一個容量為 10 的空列表,符合無參構造器中的注釋,其實就是一個延遲初始化的技巧。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 擴容操作
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* 允許分配的最大數組大小
*一些 VM 會在數組頭部儲存頭數據,試圖嘗試創建一個比 Integer.MAX_VALUE - 8 大的數組可能會產生 OOM 異常。
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 增加 ArrayList 實例的容量,確保 ArrayList 實例能存儲 minCapacity 個元素
*/
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);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
/**
* 將指定的元素追加到此列表的末尾
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/**
* 在列表中將指定元素插入到指定位置,將其后元素都向右移動一個位置
*/
public void add(int index, E element) {
//檢查 index 是否越界
rangeCheckForAdd(index);
//確保有足夠的容量能夠添加元素
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
/**
* 將指定集合中的全部元素添加到列表尾端
*/
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
/**
* 將指定集合中的全部元素插入到列表指定的位置后面
*/
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
因此對於無參構造器的注釋的疑問,到達這里就可以解答了,它確確實實初始化了一個大小為 10 的空列表,只是不是一開始就初始化,而是使用了延遲初始化的方式,在add
的時候才進行初始化。
對於另一個問題,無參構造器使用DEFAULTCAPACITY_EMPTY_ELEMENTDAT
,對於new ArrayList(0);
使用的是EMPTY_ELEMENTDATA
,前者是不知道需要的容量大小,后者預估元素較少。因此ArrayList
對此做了區別,通過引用判斷來區別用戶行為,使用不同的擴容算法(擴容速度:無參:10->15->22...,有參且參數為0 :0->1->2->3->4->6->9...)。另外,在 JDK 1.7 中,沒有通過兩個空數組來對用戶行為進行區分,因此容量為 0 的話,會創建很多空數組new Object[0]
,因此上述方式也對這種情況進行了優化。
//JDK 1.7
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
public ArrayList() {
this(10);
}
刪除
/**
* 從列表中刪除指定位置的元素,並將其后位置的元素向左移動
*/
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);
//置null,讓 GC 可以工作
elementData[--size] = null;
return oldValue;
}
/**
* 刪除列表中首次出現的指定的元素,若列表不存在相應元素,則不做改變
*/
public boolean remove(Object o) {
//若指定元素為 null,因其為空,沒有 equals 方法,因此這兩個地方做一個區分
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;
}
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
/**
* 刪除列表中所有的元素
*/
public void clear() {
modCount++;
// 置 null 以便讓 GC 回收
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
/**
* 從列表中刪除 fromIndex <= pos < toIndex 的元素
*/
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
ArrayList的序列化
在 主要字段 部分我們可以看到,elementData 是通過transient
修飾的(transient具體用法可參看Java對象序列化一文),通過transient
聲明,因此其無法通過序列化技術保存下來,但仔細閱讀源碼發現其內部實現了序列化和反序列化函數:
/**
* 保存 ArrayList 中實例的狀態到序列中
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
/**
* 從序列中恢復 ArrayList 中實例的狀態
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
通過一個例子驗證其序列化和反序列化過程:
package test;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class ListDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
List<String>list = new ArrayList<>();
list.add("hello");
list.add("world");
//write Obj to File
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("file"));
oos.writeObject(list);
oos.close();
//read Obj from File
File file = new File("file");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
List<String>newList = (List<String>) ois.readObject();
ois.close();
System.out.println(newList);
}
}
/*
[hello, world]
*/
可以得出結論:ArrayList支持進行序列化操作,此時不禁會思考既然要將 ArrayList 的字段序列化(即將 elementData 序列化),那為什么又要用 transient 修飾 elementData 呢?實際上,ArrayList 通過動態數組的技術,當數組放滿后,自動擴容,但是擴容后的容量往往都是大於或者等於 ArrayList 所存元素的個數。如果直接序列化 elementData 數組,那么就會序列化一大部分沒有元素的數組,導致浪費空間,為了保證在序列化的時候不會將這么大部分沒有元素的數組進行序列化,因此設置為 transient。
// Write out all elements in the proper order.
for (int i=0; i<size; i++)
{
s.writeObject(elementData[i]);
}
從源碼中,可以觀察到循環時是使用i < size
而不是i<elementData.length
,說明序列化時,只需實際存儲的那些元素,而不是整個數組。
問題
List<Integer>list = new ArrayList<>(10); out.println(list.get(1));
是否會拋出異常- ArrayList 支持序列化,為什么 elementData 要設置為
transient
- 對於空實例數組,為什么要區分
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
和EMPTY_ELEMENTDATA