原文地址:xeblog.cn/articles/19數組
本文主要介紹的是ArrayList在JDK8中的實現。安全
ArrayList
是一個數組隊列,內部維護一個Java數組
,而且它是動態的,數組的容量能夠自動增加。它繼承了AbstractList
,且實現了List、RandomAccess、Cloneable、Serializable
等接口。併發
優勢框架
缺點dom
stackoverflow
上有人回答說是 Josh Bloch
(Java集合框架做者)設計失誤,做者本來覺得這樣的設計是有價值的,後來發現並非。
List
接口,反射獲取接口的時候還須要先獲取父類,而後再經過父類獲取接口。ArrayList
實現了List
接口(敷衍.gif)。/** * 默認容量爲10 */
private static final int DEFAULT_CAPACITY = 10;
/** * 空數組 */
private static final Object[] EMPTY_ELEMENTDATA = {};
/** * 用於默認大小的空數組。將此與EMPTY_ELEMENTDATA區分開來,以便在添加第一個元素時知道要膨脹多少。 * 使用無參構造初始化ArrayList時默認的數組 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/** * 存儲ArrayList元素的數組。添加第一個元素時,若是elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * 則此數組的長度是默認的10 */
transient Object[] elementData;
/** * ArrayList的元素個數 */
private int size;
複製代碼
帶一個int
類型參數的構造方法,傳入的是ArrayList
的初始長度this
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 默認的空數組 Object[] EMPTY_ELEMENTDATA = {}
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
}
}
複製代碼
JDK8的無參構造spa
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
複製代碼
JDK7的無參構造線程
public ArrayList() {
this(10);
}
複製代碼
JDK8中使用默認的無參構造方法初始化ArrayList
時,作了延遲優化,在未執行add()
方法前ArrayList
數組中的實際大小仍是0
,等到第一次添加元素的時候才進行默認長度爲10
的數組初始化。設計
傳入一個集合對象的構造方法,構造一個包含此集合元素的ArrayList
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;
}
}
複製代碼
首先看add(E e)
方法
public boolean add(E e) {
// 判斷添加此元素時數組是否會超出,超出則增加數組
ensureCapacityInternal(size + 1);
// 添加元素
elementData[size++] = e;
return true;
}
複製代碼
/** * 此方法用於判斷當添加這個元素時數組容量是否超出,超出則自動增加 */
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 若是數組是經過默認構造方法實例化的,elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 將返回true
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 返回最大的值 ,若是minCapacity大於10則返回minCapacity的值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
// fail-fast機制,併發修改會拋出異常 throw new ConcurrentModificationException()
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
// 新增元素後的數組長度超過了當前數組長度,因此調用增長數組長度的方法
grow(minCapacity);
}
複製代碼
看一看grow(int minCapacity)
方法就能知道ArrayList
是如何自動增加容量的了
// 分配的最大數組大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
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;
// MAX_ARRAY_SIZE爲int的最大值減8,若是增加後的容量超過該值,則直接返回int的最大值,不然返回該值
if (newCapacity - MAX_ARRAY_SIZE > 0);
newCapacity = hugeCapacity(minCapacity);
// 使用的是Arrays.copyOf()方法將原數組中的元素拷貝到新增數組中,新增數組的長度便是newCapacity
elementData = Arrays.copyOf(elementData, newCapacity);
}
複製代碼
這裏列出hugeCapacity(int minCapacity)
方法
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
複製代碼
JDK6中 int newCapacity = (oldCapacity * 3)/2 + 1;
增加容量等於舊容量的1.5倍加1,JDK8中使用了位運算,直接增加爲舊容量的1.5倍。
在添加大量元素前,能夠經過調用ensureCapacity(int minCapacity)
方法來手動增長ArrayList
的容量,以減小遞增式再分配的數量
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);
}
}
複製代碼
trimToSize()
方法能夠將ArrayList的容量調整爲實際元素的大小
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
複製代碼
因爲ArrayList
不是線程安全的,因此若是在使用迭代器的過程當中有其它線程修改了ArrayList
,那麼將會拋出 throw new ConcurrentModificationException()
的異常,這就是fail-fast機制
(快速失敗)。
fail-fast機制
是經過modCount
字段來判斷的,modCount
字段是父類AbstractList
的字段,在每次修改ArrayList
時,modCount
字段都會自動加1,迭代器初始化時會將modCount
的值賦給迭代器的expectedModCount
字段。 ArrayList的迭代器內部實現(部分)
public Iterator<E> iterator() {
return new Itr();
}
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;
Itr() {}
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();
}
}
}
複製代碼
在執行next()、remove()
方法時都會調用checkForComodification()
方法,判斷expectedModCount
是否還等於modCount
,若是不相等則說明已經有其它線程修改了ArrayList
,這時就將拋出異常throw new ConcurrentModificationException()
。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
複製代碼