一、ArrayList 概述
ArrayList 是實現 List 接口的動態數組,所謂動態就是它的大小是可變的。實現了所有可選列表操作,並允許包括 null 在內的所有元素。除了實現 List 接口外,此類還提供一些方法來操作內部用來存儲列表的數組的大小。
每個 ArrayList 實例都有一個容量,該容量是指用來存儲列表元素的數組的大小。默認初始容量為 10。隨著 ArrayList 中元素的增加,它的容量也會不斷的自動增長。在每次添加新的元素時,ArrayList 都會檢查是否需要進行擴容操作,擴容操作帶來數據向新數組的重新拷貝,所以如果我們知道具體業務數據量,在構造 ArrayList 時可以給 ArrayList 指定一個初始容量,這樣就會減少擴容時數據的拷貝問題。當然在添加大量元素前,應用程序也可以使用 ensureCapacity 操作來增加 ArrayList 實例的容量,這可以減少遞增式再分配的數量。
注意,ArrayList 實現不是同步的。如果多個線程同時訪問一個 ArrayList 實例,而其中至少一個線程從結構上修改了列表,那麼它必須保持外部同步。所以為了保證同步,最好的辦法是在創建時完成,以防止意外對列表進行不同步的訪問:
List list = Collections.synchronizedList(new ArrayList<>());
二、ArrayList 源碼分析
ArrayList 我們使用的實在是太多了,非常熟悉,所以在這裡將不介紹它的使用方法。ArrayList 是實現 List 接口的,底層采用數組實現,所以它的操作基本上都是基於對數組的操作。
2.1、底層使用數組
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
transient??為 Java 關鍵字,為變量修飾符,如果用 transient 聲明一個實例變量,當對象存儲時,它的值不需要維持。Java 的 serialization 提供了一種持久化對象實例的機制。當持久化對象時,可能有一個特殊的對象數據成員,我們不想用 serialization 機制來保存它。為了在一個特定對象的一個域上關閉 serialization,可以在這個域前加上關鍵字 transient。當一個對象被序列化的時候,transient 型變量的值不包括在序列化的表示中,然而非 transient 型的變量是被包括進去的。
這裡 Object[] elementData,就是我們的 ArrayList 容器,下面介紹的基本操作都是基於該 elementData 變量來進行操作的。
2.2、構造函數
ArrayList 提供了三個構造函數:
ArrayList():默認構造函數,提供初始容量為 10 的空列表。
ArrayList(int initialCapacity):構造一個具有指定初始容量的空列表。
ArrayList(Collection<? extends E> c):構造一個包含指定 collection 的元素的列表,這些元素是按照該 collection 的迭代器返回它們的順序排列的。
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
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);
}
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
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;
}
}
2.3、新增
ArrayList 提供了 add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)、set(int index, E element) 這個五個方法來實現 ArrayList 增加。
add(E e):將指定的元素添加到此列表的尾部。
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
這裡 ensureCapacity() 方法是對 ArrayList 集合進行擴容操作,elementData(size++) = e,將列表末尾元素指向e。
add(int index, E element):將指定的元素插入此列表中的指定位置。
public void add(int index, E element) {
//判斷索引位置是否正確
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
//擴容檢測
ensureCapacity(size+1);
/*
* 對源數組進行復制處理(位移),從index + 1到size-index。
* 主要目的就是空出index位置供數據插入,
* 即向右移動當前位於該位置的元素以及所有後續元素。
*/
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//在指定位置賦值
elementData[index] = element;
size++;
}
在這個方法中最根本的方法就是 System.arraycopy() 方法,該方法的根本目的就是將 index 位置空出來以供新數據插入,這裡需要進行數組數據的右移,這是非常麻煩和耗時的,所以如果指定的數據集合需要進行大量插入(中間插入)操作,推薦使用 LinkedList。
addAll(Collection<? extends E> c):按照指定 collection 的迭代器所返回的元素順序,將該 collection 中的所有元素添加到此列表的尾部。
public boolean addAll(Collection<? extends E> c) {
// 將集合C轉換成數組
Object[] a = c.toArray();
int numNew = a.length;
// 擴容處理,大小為size + numNew
ensureCapacity(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
這個方法無非就是使用 System.arraycopy() 方法將 C 集合(先准換為數組)裡面的數據復制到 elementData 數組中。這裡就稍微介紹下 System.arraycopy(),因為下面還將大量用到該方法。該方法的原型為:public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)。它的根本目的就是進行數組元素的復制。即從指定源數組中復制一個數組,復制從指定的位置開始,到目標數組的指定位置結束。將源數組 src從srcPos 位置開始復制到 dest 數組中,復制長度為 length,數據從 dest 的 destPos 位置開始粘貼��
addAll(int index, Collection<? extends E> c):從指定的位置開始,將指定 collection 中的所有元素插入到此列表中。
public boolean addAll(int index, Collection<? extends E> c) {
//判斷位置是否正確
if (index > size || index < 0)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
+ size);
//轉換成數組
Object[] a = c.toArray();
int numNew = a.length;
//ArrayList容器擴容處理
ensureCapacity(size + numNew); // Increments modCount
//ArrayList容器數組向右移動的位置
int numMoved = size - index;
//如果移動位置大於0,則將ArrayList容器的數據向右移動numMoved個位置,確保增加的數據能夠增加
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
//添加數組
System.arraycopy(a, 0, elementData, index, numNew);
//容器容量變大
size += numNew;
return numNew != 0;
}
set(int index, E element):用指定的元素替代此列表中指定位置上的元素。
public E set(int index, E element) {
//檢測插入的位置是否越界
RangeCheck(index);
E oldValue = (E) elementData[index];
//替代
elementData[index] = element;
return oldValue;
}
2.4、刪除
ArrayList 提供了 remove(int index)、remove(Object o)、removeRange(int fromIndex, int toIndex)、removeAll() 四個方法進行元素的刪除。
remove(int index):移除此列表中指定位置上的元素。
public E remove(int index) {
//位置驗證
RangeCheck(index);
modCount++;
//需要刪除的元素
E oldValue = (E) elementData[index];
//向左移的位數
int numMoved = size - index - 1;
//若需要移動,則想左移動numMoved位
if (numMoved > 0)
System.arraycopy(elementData, index + 1, elementData, index,
numMoved);
//置空最後一個元素
elementData[--size] = null; // Let gc do its work
return oldValue;
}
remove(Object o):移除此列表中首次出現的指定元素(如果存在)。
public boolean remove(Object o) {
//因為ArrayList中允許存在null,所以需要進行null判斷
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;
}
其中 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; // Let gc do its work
}
removeRange(int fromIndex, int toIndex):移除列表中索引在 fromIndex(包括)和 toIndex(不包括)之間的所有元素。
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System
.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// Let gc do its work
int newSize = size - (toIndex - fromIndex);
while (size != newSize)
elementData[--size] = null;
}
removeAll():是繼承自 AbstractCollection 的方法,ArrayList 本身並沒有提供實現。
public boolean removeAll(Collection<?> c) {
boolean modified = false;
Iterator<?> e = iterator();
while (e.hasNext()) {
if (c.contains(e.next())) {
e.remove();
modified = true;
}
}
return modified;
}
2.5、查找
ArrayList 提供了 get(int index) 用讀取 ArrayList 中的元素。由於 ArrayList 是動態數組,所以我們完全可以根據下標來獲取 ArrayList 中的元素,而且速度還比較快,故 ArrayList 長於隨機訪問。
public E get(int index) {
RangeCheck(index);
return (E) elementData[index];
}
2.6、擴容
在上面的新增方法的源碼中我們發現每個方法中都存在這個方法: ensureCapacity(),該方法就是 ArrayList 的擴容方法。在前面就提過 ArrayList 每次新增元素時都會需要進行容量檢測判斷,若新增元素後元素的個數會超過 ArrayList 的容量,就會進行擴容操作來滿足新增元素的需求。所以當我們清楚知道業務數據量或者需要插入大量元素前,我可以使用 ensureCapacity 來手動增加 ArrayList 實例的容量,以減少遞增式再分配的數量。
public void ensureCapacity(int minCapacity) {
//修改計時器
modCount++;
//ArrayList容量大小
int oldCapacity = elementData.length;
/*
* 若當前需要的長度大於當前數組的長度時,進行擴容操 作
*/
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
//計算新的容量大小,為當前容量的1.5倍
int newCapacity = (oldCapacity * 3) / 2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
//數組拷貝,生成新的數組
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
在這裡有一個疑問,為什麼每次擴容處理會是 1.5 倍,而不是 2.5、3、4 倍呢?通過 google 查找,發現 1.5 倍的擴容是最好的倍數。因為一次性擴容太大(例如 2.5 倍)可能會浪費更多的內存(1.5 倍最多浪費 33%,而 2.5 被最多會浪費 60%,3.5 倍則會浪費 71%……)。但是一次性擴容太小,需要多次對數組重新分配內存,對性能消耗比較嚴重。所以 1.5 倍剛剛好,既能滿足性能需求,也不會造成很大的內存消耗。
處理這個 ensureCapacity() 這個擴容數組外,ArrayList 還給我們提供了將底層數組的容量調整為當前列表保存的實際元素的大小的功能。它可以通過 trimToSize() 方法來實現。該方法可以最小化 ArrayList 實例的存儲量。
public void trimToSize() {
modCount++;
int oldCapacity = elementData.length;
if (size < oldCapacity) {
elementData = Arrays.copyOf(elementData, size);
}
}