ArrayList概述:
ArrayList是List接口基於數組的實現。它允許包括 null 在內的所有元素。每個ArrayList實例都有一個容量,該容量代表可以存儲元素的多少。每個ArrayList實例都有一個初始大小,當然你也可以通過構造方法指定初始大小。隨着向ArrayList中不斷添加元素,其容量也自動增長。當元素數量達到當前容量最大值時會導致數據向新數組的重新拷貝,因此,如果可預知數據量的多少,可在構造ArrayList時指定其容量。在添加大量元素前,應用程序也可以使用ensureCapacity操作來增加ArrayList實例的容量,這可以減少遞增式再分配的數量。 但是需要注意,此方法的實現不是同步的。如果多個線程同時訪問一個ArrayList實例,而其中至少一個線程從結構上修改了列表,那麼它必須保持外部同步。
底層探究
- 1 初始容量
private static final int DEFAULT_CAPACITY = 10;
- 2 底層存儲
/**
*Object類型數組
*/
transient Object[] elementData;
- 3 構造方法
/**
* 指定初始容量
*/
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;
}
/**
* 使用一個集合初始化一個ArrayList
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
- 4 存儲
ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection
//設置指定位置元素的值(可理解爲更新操作)
set(int index, E element)
//往集合中添加元素,如果容量不足則擴容
add(E e)
//往指定位置插入元素,如果容量不足則先擴容。原先index及之後的元素均往後移動一位
add(int index, E element)
- 5 刪除
//刪除指定位置的元素,原先index之後的元素均往前移動一位
public E remove(int index) ;
//移除此列表中首次出現的指定元素(如果存在),此操作需要遍歷數組
public boolean remove(Object o) {
以上介紹了ArrayList的增刪等操作,下面再來看下源碼
仔細觀察我們會發現還有兩個空數組
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
這兩個空數組有什麼用呢?我們再把上面的兩個構造方法拿來看下:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_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);
}
}
我們可以看到,當使用默認構造方法時,直接將DEFAULTCAPACITY_EMPTY_ELEMENTDATA賦值給了elementData,當使用指定容量進行構造ArrayList時,如果initialCapacity=0,將EMPTY_ELEMENTDATA賦值給了elementData,這兩種方式構造出來的ArrayList初始容量均爲0,那麼爲什麼要使用兩個不同的空數組呢?想知道答案我們來看下add(E e);方法:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
從上面代碼可以看到,在add一個元素時,纔對容量進行了操作,爲什麼要把擴容操作放在這裏呢?其實是防止你new出一個數組,但是不用,導致空間資源的浪費。回到上面的問題,我們看下ensureCapacityInternal方法:
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
看下這裏有個if判斷,當elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA時,對容量有一個賦值操作,minCapacity爲DEFAULT_CAPACITY與minCapacity的最大值。顯然當第一次add元素時minCapacity==1所以初始容量就爲DEFAULT_CAPACITY也就是10了。所以使用兩個不同的空數組的原因就是爲了保證:當我們使用默認構造方法對ArrayList進行構造時,初始容量爲10。
下面再來看下ensureExplicitCapacity和grow方法:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//當最小所需容量大於當前數組容量時進行grow操作
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
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);
}
可以看出,當最小所需容量大於當前數組容量時進行grow操作。先嚐試新容量爲舊熔鍊個的1.5倍,如果還不夠,那麼就使用minCapacity作爲當前擴充的容量大小。
此外有沒有注意到ensureExplicitCapacity方法中有一個modCount++的操作,這個modCount是幹什麼用的呢?
ArrayList的modCount是從類 java.util.AbstractList 繼承的字段:
protected transient int modCount
這個字段代表已從結構上修改此列表的次數。從結構上修改是指更改列表的大小(也就是元素個數發生了變化),或者打亂列表,從而使正在進行的迭代產生錯誤的結果。
此字段由 iterator 和 listIterator 方法返回的迭代器和列表迭代器實現使用。如果意外更改了此字段中的值,則迭代器(或列表迭代器)將拋出 ConcurrentModificationException 來響應 next、remove、previous、set 或 add 操作。在迭代期間面臨併發修改時,它提供了快速失敗 行爲,而不是非確定性行爲。
大家可以測試下下面這段程序:
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(10);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
if (integer == 10)
list.remove(integer); //注意這個地方,此處換成list.add(integer)結果是一樣的
}
}
上面這段代碼會拋出如下異常:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at Main.main(Main.java:12)
爲何會報這個錯?看下ArrayList的Iterator源碼(有部分代碼未貼出):
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@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];
}
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();
}
}
我們可以看到當做next()、remove()操作時,都要先checkForComodification()而這個方法做的事情就是判斷modCount 與expectedModCount是否相等。不相等就報錯。