一、概述
ArrayList類是AbstractList的子類,實現了具體的add(), set(), remove()等方法。它是一個可調整大小的數組可以用來存放各種形式的數據。
二、源碼分析
(1) 類的聲明
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList類繼承自AbstractList類,實現了List、RandomAccess、Cloneable、Serializable四個接口。具體作用如下:
* 繼承AbstractList類,可以自己實現add(), set(), remove()等方法,還可以用AbstractList類的modCount來實現快速失敗。
* 實現List接口,一是爲了增加可讀性,清晰看到實現的接口,二是降低維護成本,如果AbstractList類不實現List了,ArrayList類也不受影響。
* RandomAccess接口是標記接口,表示實現它的類支持快速隨機訪問。在使用循環遍歷List的時候應該判斷下這個集合是否是RandomAccess的實例,如果是就是用for循環來操作,如果不是就是使用iterator迭代器來操作。隨機訪問一般是通過index下標訪問,行爲類似數組的訪問。而順序訪問類似於鏈表的訪問,通常爲迭代器遍歷。以List接口及其實例爲例。ArrayList是典型的隨機訪問型,而LinkedList則是順序訪問型。
* Cloneable接口也是克隆標記接口,表示此類可以被克隆,此類的實例可以調用clone()方法;未實現Cloneable接口的類的實例調用clone()方法會報錯,在Object類中已經定義。
* Serializable接口是序列化標記接口,表示此類可以被序列化到內存中。目的是爲類可持久化,比如在網絡傳輸或本地存儲,爲系統的分佈和異構部署提供先決條件。
(2) 成員變量
//serialVersionUID適用於java序列化機制。簡單來說,JAVA序列化的機制是通過判斷類的serialVersionUID來驗證的版本一致的。
private static final long serialVersionUID = 8683452581122892189L;
//定義初始容量10
private static final int DEFAULT_CAPACITY = 10;
//定義一個空數組,使用場景爲初始容量爲10或者構造函數中傳入空集合時,下面源碼中可看到具體使用場景。
private static final Object[] EMPTY_ELEMENTDATA = {};
//定義一個空數組,使用場景是調用無參構造函數時,下面源碼中可看到具體使用場景。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//transient修飾的Object類型的數組,表示此數組不支持序列化。也是ArrayList的內部容器。
transient Object[] elementData; // non-private to simplify nested class access
//數組的實際元素數量
private int size;
//定義一個數組對象最大容量,只是用來做比較,ArrayList能達到的最大容量還是Integer.MAX_VALUE
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
(3) 構造方法
//無參構造函數,直接將DEFAULTCAPACITY_EMPTY_ELEMENTDATA賦值給內部容器elementData
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//指定容量的構造函數
public ArrayList(int initialCapacity) {
//如果指定容量大於0,直接賦值一個指定容量的Object數組
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//若指定容量等於0,則將EMPTY_ELEMENTDATA賦值給內部容器elementData
this.elementData = EMPTY_ELEMENTDATA;
} else {
//如果指定容量小於0,則拋出異常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//指定集合的構造函數
public ArrayList(Collection<? extends E> c) {
//直接將集合轉爲數組賦值給內部容器elementData
elementData = c.toArray();
//先將elementData的大小賦值給size,再判斷size是否爲0
if ((size = elementData.length) != 0) {
//如果轉換後的elementData不是Object[]類型,則調用Arrays.copyOf()方法賦值
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//如果size爲0,則將EMPTY_ELEMENTDATA賦值給內部容器elementData
this.elementData = EMPTY_ELEMENTDATA;
}
}
由源碼看出,每次size判斷爲0時,都是將EMPTY_ELEMENTDATA賦值給了內部容器elementData。
(4) add()方法
public boolean add(E e) {
//第一步就是確定容量
ensureCapacityInternal(size + 1); // Increments modCount!!
//把元素添加到末尾的位置
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//如果此時,是無參構造函數創建的ArrayList添加元素,那麼設置DEFAULT_CAPACITY與minCapacity中大的那個數作爲容量。也就是add()第一個元素的時候,容量就爲10了。
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
//父類的屬性,用於標記修改次數,在併發修改時,用於快速失敗
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
//動態擴容核心邏輯方法
grow(minCapacity);
}
private void grow(int minCapacity) {
//獲取原容量oldCapacity
int oldCapacity = elementData.length;
//獲取新容量newCapacity爲原容量oldCapacity的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新容量newCapacity還是小於目標容量minCapacity,那麼設置新容量就爲目標容量minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新容量newCapacity大於MAX_ARRAY_SIZE,也就是Integer.MAX_VALUE-8,則調用hugeCapacity()方法
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//擴容成功後,複製舊數組到新數組中
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
//如果minCapacity < 0,也就是minCapacity已經超過了int的最大值Integer.MAX_VALUE,則拋出內存溢出錯誤
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//判斷目標容量minCapacity是否大於MAX_ARRAY_SIZE,如果大於了,就返回Integer.MAX_VALUE,沒有超過就返回MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
add()方法的擴容機制,總結起來分爲以下兩步:
* 添加一個元素,沒超過默認容量大小10的時候,容量不變;超過後變爲原來的1.5倍。
* 添加多個元素,超過了默認容量大小是,先擴容到原容量的1.5倍, 然後與目標容量做比較,誰大就用誰。然後進行邊界判斷,就是判斷擴容後的容量是否已經大於了MAX_ARRAY_SIZE:如果小於MAX_ARRAY_SIZE,則還是用新擴容的容量;如果大於了MAX_ARRAY_SIZE,則需要對目標容量進行判斷。a) 目標容量小於0,其實就是已經越界了,那麼就直接拋出錯誤;b) 目標容量如果也大於了MAX_ARRAY_SIZE,則將新容量設置爲Integer.MAX_VALUE;c) 若目標容量小於MAX_ARRAY_SIZE,則設置新容量爲MAX_ARRAY_SIZE。
值得一提的是,ArrayList的元素插入操作,不是將對應位置的元素覆蓋,而是將該位置的元素已經後面的元素全部向後移動,騰出目標位置給目標元素存放。所以這種操作的費時費力程度與插入元素離末尾位置的距離成正比,也就是說操作的位置裏離末尾越遠就越費時費力。
(5) add()系列的其他方法
//在指定座標處添加元素
public void add(int index, E element) {
//下標校驗調用方法rangeCheckForAdd(),判斷是否越界
rangeCheckForAdd(index);
//下面的操作和add()方法基本一致
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;
}
//從指定座標index處開始,批量插入一個集合中的所有元素
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;
}
//插入操作時,進行下標校驗
private void rangeCheckForAdd(int index) {
//插入目標位置index不能大於元素個數。
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
操作步驟與add()方法也大致相同,但是在指定下標index處插入指定集合c時,需要先將index以及後面的元素向後移動c的元素個數,以保證能存下目標集合c所有的元素。
(6) set()方法修改元素
public E set(int index, E element) {
//慣例檢查下標是否越界
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
private void rangeCheck(int index) {
//此處的判斷規則是,需要修改的下標index只能小於容器元素個數size,和此容器容量無關。與add()方法的判斷規則完全不同。此處也沒有判斷index>0,是因爲elementData()方法數組取值時,可以直接拋出異常
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//生成錯誤信息
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
//返回數組對應下標下的元素
E elementData(int index) {
return (E) elementData[index];
}
(7) remove()方法移除元素
public E remove(int index) {
//慣例檢查是否越界,與set()用的一個方法
rangeCheck(index);
//監測修改次數,用於快速失敗
modCount++;
//獲取原來index座標下的元素
E oldValue = elementData(index);
//判斷移除的元素位置是否不在末尾
int numMoved = size - index - 1;
//numMoved>0表示移除的元素位置是不在末尾的
if (numMoved > 0)
//移除指定位置元素,並將後面的元素向前移
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//如果在末尾,直接將最後一位的元素設置爲null,清除後讓GC回收
elementData[--size] = null; // clear to let GC do its work
//返回舊值
return oldValue;
}
remove()方法不會引起數組的容量變化。
(8) remove()系列的其他方法
//移除指定元素
public boolean remove(Object o) {
//判斷參數元素o是否爲null,爲null採用==比較,不爲null採用equals比較
if (o == null) {
for (int index = 0; index < size; index++)
//如果相同則刪除,然後return了,所以remove(Object o)方法只會刪除集合第一個與傳入對象相同的元素。
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
//不爲null採用equals比較
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
//與remove(int index)一致,少了範圍校驗和返回舊元素
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
}
//移除fromIndex到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;
}
//移除集合c中包含的所有元素
public boolean removeAll(Collection<?> c) {
//調用Object的工具類Objects的requireNonNull()方法進行非空校驗
Objects.requireNonNull(c);
return batchRemove(c, false);
}
//批量移除
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
//如果elementData[r]這個元素不包含在集合c中,則將這個元素從座標0開始往後放,也就是將不移除的元素前移
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
//把座標w到size的元素全部置爲null,以便GC回收
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
需要注意的是,需要遍歷ArrayList的時候,只能用它實例的迭代器進行迭代,不能直接採用for循環等直接比那裏,會拋出異常。
(9) trimToSize()瘦身方法
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
這個方法會將自動擴容後,沒有被元素填充的位置清除掉,也就是這個方法執行後,size應該與容量相等。
(10) isEmpty()方法
public boolean isEmpty() {
return size == 0;
}
判斷ArrayList類的實例是否沒有元素,但不是null,判斷的是size是否爲0。
(11) contains()方法
//判斷是否含有目標元素O
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
//返回元素o所在下標
public int indexOf(Object o) {
//判斷是否爲null,如果是則採用==判斷
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
////判斷是否爲null,如果不是則採用equals()方法判斷
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
//不存在則返回-1
return -1;
}
indexOf()方法也只是返回找到的第一個符合條件的元素的下標,後面還有也不管。
(12) lastIndexOf()方法
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
lastIndexOf()方法也只是返回找到的第一個符合條件的元素的下標,但是是從後往前找,前面還有也不管。
(13) clone()方法
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
ArrayList的本質是維護了一個Object的數組,所以克隆也是通過數組的複製實現的,屬於淺複製。想要深克隆,需要循環克隆每個對象元素。
(14) clear()方法
public void clear() {
modCount++;
//循環設置每一個原有元素爲null
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
還有其他簡單的方法就不講解了。ArrayList類和AbstractList類類似,也有兩個內部類 Iterator, ListIterator ,它們都是迭代器的實現類,分別爲 Itr,ListItr;還有內部類SubList類,都可以查看《我的jdk源碼(八):AbstractList List家族的骨架實現類》 中的詳細介紹。這裏再做一下簡單的概括:
* Iterator類型的迭代器Itr對象,只能向後遍歷,可以調用Itr.remove()移除元素,但是不能新增元素。
* ListIterator類型的迭代器ListItr對象相比Iterator類型的Itr對象要多一個向前遍歷的功能,並且在Iterator的基礎上,還能調用add()方法添加新的元素。
* SubList返回的是原集合的視圖,也就是說,如果對 subList 出來的集合進行修改或新增操作,那麼原始集合也會發生同樣的操作。想要獨立出來一個集合就需要重新new一個新的對象,代碼如下:
List<String> subList = new ArrayList<>(list.subList(0, 1));
三、總結
學習ArrayList類主要是關注它的動態擴容,以及size和容量間的區別。突然想到一題分享給大家,源碼如下:
ArrayList l = new ArrayList<String>(2);
l.add("1");
l.set(1,"2");
提問:這段代碼執行後,l中變成什麼內容?
答案:這是陷阱題,set()方法執行的時候會報錯,雖然初始化指定了容量爲2,但是add()方法執行後,size是1,set()方法是改變目標座標的值,而座標爲1的地方是沒有東西的,所以會拋出下標越界的異常。可以結合上文中的set()方法源碼加深理解。
好了,這就是ArrayList類的內容,敬請期待下一篇《我的jdk源碼(十二):LinkedList類》。更多精彩內容,掃碼關注我的微信公衆號【Java覺淺】,獲取第一時間更新!