0x00 描述
ArrayList
可以說是 Java
程式猿最為常用的一種資料結構了。ArrayList
是通過陣列實現的,容量可以自增的線性表。而陣列的優點是計算機可以通過下標計算訪問地址,所以訪問元素的速度是很快的,時間複雜度為O(1);但陣列並不擅長插入和刪除操作,這些操作的時間複雜度是O(n)。因此 ArrayList
繼承了陣列這些特點。
繼承關係
ArrayList
繼承於 AbstractList
並實現了 List
、RandomAccess
、Cloneable
和 Serializable
介面。
而 AbstractList
是繼承於 Collection
介面。因此簡單的關係圖可以表達為
重要屬性
elementData
這個是存放資料的Object
陣列size
記錄當前陣列元素的個數modCount
用於記錄修改次數,例如增加、刪除等操作時此變數會自增。當這個變數異常變化時,會丟擲ConcurrentModificationException
構造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
複製程式碼
預設建構函式初始化 elementData
大小為10 空陣列。
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);
}
}
複製程式碼
此方法通過一個 initialCapacity
變數對陣列進行初始化。當傳入的 initialCapacity
大於0時,elementData就是初始化為大小為 initialCapacity
的空陣列;否則就是初始化為大小為0的空陣列。
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;
}
}
複製程式碼
這個方法是通過一個 Collection
物件進行初始化的。這裡呼叫了 Arrays.copyOf
方法將陣列元素進行拷貝,並返回一個新的陣列。後文會詳細解析這個方法。
0x01 常用方法
add(E e)
給 ArrayList
新增一個元素
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
複製程式碼
新增元素之前,呼叫了 ensureCapacityInternal
方法,確保 elementData
陣列有足夠的空間。然後陣列後面新增一個元素,並把元素個數 size
的值加1。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
複製程式碼
在 ensureCapacityInternal
中先判斷 elementData
是否為空陣列,如果是,則取 DEFAULT_CAPACITY
與 minCapacity
的最大值作為陣列的最小容量。
然後再執行 ensureExplicitCapacity
方法。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
複製程式碼
先把 modCount
加1,表示對該列表進行了一次操作。
minCapacity
表示目前需要的容量大小。如果它大於目前 elementData
的容量大小,那麼就會執行 grow
方法增加陣列容量。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//右移1位操作相當於除2
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);
}
複製程式碼
該方法的邏輯是
- 先獲取到
newCapacity
,它是原來容量大小的1.5倍 - 如果需要的容量大小
minCapacity
大於原來容量的1.5,那麼newCapacity
就取minCapacity
- 如果
newCapacity
還大於最大的容量,那麼就執行hugeCapacity
來計算得到容量大小 - 最後呼叫
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;
}
複製程式碼
因此在呼叫 add
方法時,如果當前陣列 elementData
的容量不夠時,就會呼叫擴容的 grow
方法,把陣列擴大為原來的1.5倍的大小。
add(int index, E element)
在指定的 index
位置上新增一個元素
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
複製程式碼
通過上面的 add
方法的走讀,這個方法就很好理解了。
先對 index
引數的有效性進行判斷;
然後執行 ensureCapacityInternal
確保陣列的容量大小是足夠的,此時 modCount
也會自增;
再執行 System.arraycopy
方法把陣列元素從 index
的位置後移1位;(System.arraycopy
函式後文還會講到)
最後在 index
位置上賦值,並把 size
加 1。
addAll(Collection<? extends E> c)
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;
}
複製程式碼
addAll
方法把一個 Collection
物件新增到列表中來。
它會先把 Collection
物件通過 toArray
方法轉化為陣列,然後再呼叫 System.arraycopy
進行資料的移動。
addAll(int index, Collection<? extends E> c)
public boolean addAll(int index, Collection<? extends E> c) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(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;
}
複製程式碼
在 index
位置上新增一個列表
它與上面 addAll
方法的區別就是先從 index
開始移動 numNew
個位置,即空出 numNew
個位置。
然後再空出的 numNew
位置上新增元素。
remove(int index)
刪除指定 index
位置上的元素
public E remove(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
modCount++;
E oldValue = (E) elementData[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;
}
複製程式碼
執行流程為
- 檢查
index
有效性,無效則丟擲IndexOutOfBoundsException
異常 modCount
自增- 通過
index
下標取出元素 - 計算
index
後面需要移動的元素個數 - 通過
System.arraycopy
將index
後面的元素都往前移動1位 - 最後把末尾元素置位
null
,並把size
的值減 1。
remove(Object o)
通過一個元素物件進行刪除
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;
}
複製程式碼
當傳一個元素物件進行刪除操作時,需要遍歷陣列,找到該元素在列表中的位置 index
;
然後通過 fastRemove
方法進行刪除。
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
}
複製程式碼
clear()
清空列表
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
複製程式碼
subList(int fromIndex, int toIndex)
獲取子列表
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
static void subListRangeCheck(int fromIndex, int toIndex, int size) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if (toIndex > size)
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex +") > toIndex(" + toIndex + ")");
}
複製程式碼
首先檢查下標是否正確,然後構造一個 SubList
物件,這是一個內部類。
SubList
也是繼承於 AbstractList
。
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size;
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
public E set(int index, E e) {
if (index < 0 || index >= this.size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
E oldValue = (E) ArrayList.this.elementData[offset + index];
ArrayList.this.elementData[offset + index] = e;
return oldValue;
}
public E get(int index) {
if (index < 0 || index >= this.size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
return (E) ArrayList.this.elementData[offset + index];
}
...
public void add(int index, E e) {
if (index < 0 || index > this.size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
public E remove(int index) {
if (index < 0 || index >= this.size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
this.size--;
return result;
}
...
public boolean addAll(Collection<? extends E> c) {
return addAll(this.size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
if (index < 0 || index > this.size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
int cSize = c.size();
if (cSize==0)
return false;
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
parent.addAll(parentOffset + index, c);
this.modCount = parent.modCount;
this.size += cSize;
return true;
}
...
}
複製程式碼
SubList
構造方法需要一個父列表。在獲取、新增、刪除元素的方法中實際上都是呼叫父列表中的方法。
不過這些操作的方法中會判斷 modCount
的值是否已經變化,如果異常改變了,那麼就會丟擲 ConcurrentModificationException
異常。
forEach(Consumer<? super E> action)
遍歷列表元素
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = 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]);
}
// Android-note:
// Iterator will not throw a CME if we add something while iterating over the *last* element
// forEach will throw a CME in this case.
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
複製程式碼
同樣地,此方法中如果 modCount
被異常修改了(例如在其他執行緒中執行了 add
方法)那麼就會丟擲 ConcurrentModificationException
異常。
iterator()
獲取遍歷器
public Iterator<E> iterator() {
return new Itr();
}
複製程式碼
Itr
是一個內部類,實現了 Iterator
介面。
private class Itr implements Iterator<E> {
// Android-changed: Add "limit" field to detect end of iteration.
// The "limit" of this iterator. This is the size of the list at the time the
// iterator was created. Adding & removing elements will invalidate the iteration
// anyway (and cause next() to throw) so saving this value will guarantee that the
// value of hasNext() remains stable and won't flap between true and false when elements
// are added and removed from the list.
protected int limit = ArrayList.this.size;
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 < limit;
}
@SuppressWarnings("unchecked")
public E next() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
int i = cursor;
if (i >= limit)
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();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
limit--;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
...
}
複製程式碼
該類中 cursor
屬性記錄了當前迭代的位置,每呼叫一次 next
方法都會加 1,lastRet
則記錄了上一次的元素位置。
remove
方法則是通過呼叫外部類的 remove
方法來實現的。
以上兩個方法中也需要注意 ConcurrentModificationException
異常的發生。
contains(Object o)
檢測是否包含元素
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
複製程式碼
可以看出要檢測一個元素是否在列表中,是通過遍歷來實現的。
System.arraycopy
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos,int length);
複製程式碼
這是陣列拷貝函式,是 native
函式,它經過虛擬機器優化的,效率比較高。在 ArrayList
中移動元素就是通過這個方法。
Arrays.copyOf
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;
}
複製程式碼
可以看出 copyOf
函式最終呼叫的是 System.arraycopy
方法。本文中 grow
方法就是呼叫 copyOf
來實現擴容的。
0x02 總結
ArrayList
是基於陣列實現的線性表,它支援自動擴容,每次增加原來容量的1.5倍。- 通過下標獲取元素操作效率高,而刪除和插入操作則需要移動元素,效率不高。
remove
函式通過物件刪除元素時需要遍歷列表,而通過下標index
刪除元素比通過物件刪除元素的效率要高。containts
與clear
方法需要遍歷。subList
獲取到子列表,對子列表的修改同樣也會修改父列表。ArrayList
沒有同步鎖,在多執行緒操作時需要注意ConcurrentModificationException
異常。- 如果在同一執行緒中對
ArrayList
操作時引起modCount
異常改變時,也要注意ConcurrentModificationException
,這時候要檢查程式碼邏輯問題。