公衆號原文:ArrayList 源碼分析
博客原文:ArrayList 源碼分析
如下源碼分析使用的 Java 版本爲 1.8java
ArrayList 是基於數組實現的,繼承 AbstractList, 實現了 List、RandomAccess、Cloneable、Serializable 接口,支持隨機訪問。設計模式
java.util public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
複製代碼
List list = Collections.synchronizedList(new ArrayList(...));
ConcurrentModificationException
異常,可是快速失敗行爲不是硬保證的,只是盡最大努力當添加第一個元素時,elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
的任何空ArrayList都將擴展爲默認的capacity數組
private static final int DEFAULT_CAPACITY = 10; // 默認容量大小
private static final Object[] EMPTY_ELEMENTDATA = {}; // ArrayList空實例共享的一個空數組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // ArrayList空實例共享的一個空數組,用於默認大小的空實例。與 EMPTY_ELEMENTDATA 分開,這樣就能夠了解當添加第一個元素時須要建立多大的空間
transient Object[] elementData; // 真正存儲ArrayList中的元素的數組
private int size; // 存儲ArrayList的大小,注意不是elementData的長度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // 數組的最大長度
protected transient int modCount = 0; //AbstractList類的,表示 elementData在結構上被修改的次數,每次add或者remove它的值都會加1
複製代碼
// 無參構造方法,默認初始容量10
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);
}
}
// 經過一個容器來初始化
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) { // c.toArray 返回的可能不是 Object[]
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA; // replace with empty array.
}
}
複製代碼
添加元素時使用 ensureCapacityInternal() 方法來保證容量足夠,size + 1
爲最少須要的空間大小,若是elementData的長度不夠時,須要使用 grow() 方法進行擴容安全
// 添加一個元素
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
// 計算最少須要的容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 默認的空實例第一次添加元素時,使用默認的容量大小與minCapacity的最大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0) // 須要的容量大於elementData的長度
grow(minCapacity); // 進行擴容
}
複製代碼
擴容:當新容量小於等於 MAX_ARRAY_SIZE
時,新容量的大小爲 oldCapacity + (oldCapacity >> 1)
與 minCapacity
之間的較大值 ,也就是舊容量的 1.5 倍與 minCapacity
之間的較大值bash
private void grow(int minCapacity) {
int oldCapacity = elementData.length; // 本來的容量
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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
複製代碼
最後調用 Arrays.copyOf
複製原數組,將 elementData 賦值爲獲得的新數組。因爲數組複製代價較高,因此建議在建立 ArrayList 對象時就指定大概的容量大小,減小擴容操做的次數微信
public class Arrays {
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
//...
}
複製代碼
經過 addAll 添加一個集合中全部元素時的擴容:至少須要的容量爲兩個集合的長度之和,一樣是經過 ensureCapacityInternal() 來保證容量是足夠的,而後調用 System.arraycopy
將要添加的集合中的元素複製到原集合已有元素的後面多線程
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;
}
複製代碼
刪除指定下標的元素時,若是下標沒有越界,則取出下標對應的值,而後將數組中該下標後面的元素都往前挪1位,須要挪的元素數量是 size - index - 1
,時間複雜度爲 O(n),因此刪除元素的代價挺高併發
public E remove(int index) {
rangeCheck(index); // 檢查下標是否在數組的長度範圍內
modCount++;
E oldValue = elementData(index); // 下標爲index的值
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
return oldValue;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
複製代碼
刪除在指定集合中的全部元素 removeAll,刪除不在指定集合中的全部元素 retainAlldom
這二者都是經過 batchRemove
來批量刪除源碼分析
// 刪除在指定集合中的全部元素
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c); // c 不能爲null
return batchRemove(c, false);
}
// 刪除不在指定集合中的全部元素,也就是隻保留指定集合中的元素,其它的都刪除掉
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
// 批量刪除
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0; // r爲當前下標,w爲當前須要保留的元素的數量(或者說是下一個需保留元素的下標)
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement) // 判斷元素 elementData[r] 是否須要刪除
elementData[w++] = elementData[r];
} finally {
// r != size 的狀況多是 c.contains() 拋出了異常,將 r 以後的元素複製到 w 以後
if (r != size) {
System.arraycopy(elementData, r, elementData, w, size - r);
w += size - r;
}
if (w != size) {
// w 以後的元素設置爲 null 以讓 GC 回收
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
複製代碼
刪除第一個值爲指定值的元素 remove(Object o)
,參數 o 能夠爲 null
fastRemove(int index)
與 remove(int index)
幾乎同樣,只不過不返回被刪除的元素
public boolean remove(Object o) {
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 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
}
複製代碼
ArrayList 支持三種方式:
迭代器 Iterator 和 ListIterator 的主要區別::
ArrayList 的迭代器 Iterator 和 ListIterator 在《設計模式 | 迭代器模式及典型應用》這篇文章中有過詳細介紹,這裏只作一個小結
foreach 循環:
foreach 循環涉及到一個 Consumer 接口,接收一個泛型的參數T,當調用 accept 方法時,Stream流中將對 accept 的參數作一系列的操做
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount; // 記錄當前的 modCount
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
複製代碼
ArrayList 有兩個屬性被 transient 關鍵字
修飾,transient 關鍵字 的做用:讓某些被修飾的成員屬性變量不被序列化
transient Object[] elementData;
protected transient int modCount = 0;
複製代碼
爲何最爲重要的數組元素要用 transient 修飾呢?
跟Java的序列化機制有關,這裏列出Java序列化機制的幾個要點:
因此問題的答案是:ArrayList 不想用Java序列化機制的默認處理來序列化 elementData 數組,而是經過 readObject、writeObject 方法自定義序列化和反序列化策略。
問題又來了,爲何不用Java序列化機制的默認處理來序列化 elementData 數組呢?
答案是由於效率問題,若是用默認處理來序列化的話,若是 elementData 的長度有100,可是實際只用了50,其實剩餘的50是能夠不用序列化的,這樣能夠提升序列化和反序列化的效率,節省空間。
如今來看 ArrayList 中自定義的序列化和反序列化策略
private static final long serialVersionUID = 8683452581122892189L;
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
int expectedModCount = modCount;
s.defaultWriteObject(); // 默認的序列化策略,序列化其它的字段
s.writeInt(size); // 實際用的長度,而不是容量
for (int i=0; i<size; i++) { // 只序列化數組的前 size 個對象
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
s.readInt(); // ignored
if (size > 0) {
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
ensureCapacityInternal(size);
Object[] a = elementData;
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
複製代碼
modCount 用來記錄 ArrayList 結構發生變化的次數,若是一個動做先後 modCount 的值不相等,說明 ArrayList 被其它線程修改了
若是在建立迭代器以後的任什麼時候候以任何方式修改了列表(增長、刪除、修改),除了經過迭代器本身的remove 或 add方法,迭代器將拋出 ConcurrentModificationException
異常
須要注意的是:這裏異常的拋出條件是檢測到 modCount != expectedmodCount
,若是併發場景下一個線程修改了modCount值時另外一個線程又 "及時地" 修改了expectedmodCount值,則異常不會拋出。因此不能依賴於這個異常來檢測程序的正確性。
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
int expectedModCount = modCount; // 記錄下當前的 modCount
// 一些操做以後....
if (modCount != expectedModCount) { // 比較如今與以前的 modCount,不相等表示在中間過程當中被修改了
throw new ConcurrentModificationException();
}
}
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
int expectedModCount = modCount;
// 一些操做以後....
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
public void forEach(Consumer<? super E> action) {
final int expectedModCount = modCount;
// 一些操做以後....
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
public boolean removeIf(Predicate<? super E> filter) {
final int expectedModCount = modCount;
// 一些操做以後....
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
public void replaceAll(UnaryOperator<E> operator) {
final int expectedModCount = modCount;
// 一些操做以後....
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++; // 修改了要加一
}
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
// 一些操做以後....
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
// 內部迭代器
private class Itr implements Iterator<E> {
public void forEachRemaining(Consumer<? super E> consumer) {
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
複製代碼
歡迎評論、轉發、分享,您的支持是我最大的動力
更多內容可訪問個人我的博客:laijianfeng.org
關注【小旋鋒】微信公衆號,及時接收博文推送