ArrayList 是使用的最爲普遍的一個容器。ArrayList 的類的繼承層次圖以下:java
ArrayList 實現了 Collection
和 List
接口,同時也實現了 Cloneable
、RandomAccess
,因此 ArrayList 能夠被拷貝以及具備隨機訪問的特性。數組
本文基於 JDK1.8安全
在 ArrayList 類的頭部,定義瞭如下幾個成員變量。微信
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size;
複製代碼
這幾個變量構成了 ArrayList 的基礎。數據結構
DEFAULT_CAPACITY
表示 ArrayList 的初始容量。elementData
是存儲具體數據的數組,也就是是說,ArrayList 底層數據結構就是一個數組,size
表示 ArrayList 中元素的個數。EMPTY_ELEMENTDATA
表示一個空的 ArrayList 對象,但 ArrayList 中沒有數據時,elementData 指向的就是這個數組對象。DEFAULTCAPACITY_EMPTY_ELEMENTDATA
也表示空的 ArrayList,它只會在實例化一個不帶參數的 ArrayList 的時候被使用一次:多線程
ArrayList list = new ArrayList(); // 此時 elementData 指向的就是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
複製代碼
這些變量中須要注意的是 elementData 是不帶訪問修飾符的,這是爲了讓 ArrayList 的內部類能夠方便的訪問它,ArrayList 的內部類後面會講到。elementData 變量是使用 transient 來修飾的,這表示在序列化的時候 elementData 是不會被序列化的,具體的序列化方式後面再講。dom
在上面說到 DEFAULT_CAPACITY
是 ArrayList 的默認容量,值是10,可是須要注意的是,默認容量不必定用的上,在實例化 ArrayList 的時候分三種狀況,第一種不給構造函數傳參,可是此時會新建一個長度爲 10 的對象數組。而是在添加第一個元素的時纔會建立一個長度爲 10 的數組,並把第一個元素添加進去。第二種狀況會給構造參數傳值 n,若是 n 大於0,那麼就會直接建立一個長度爲 n 的對象數組,若是 n 等於 0,那麼就會把 EMPTY_ELEMENTDATA 賦值給 elementData。函數
第三種實例化特殊一點,是直接傳入另外一個容器對象 c 來初始化 ArrayList 對象,此時會先檢查 c 的長度,若是 c 容器裏面沒有元素,直接把 EMPTY_ELEMENTDATA 賦值給 elementData,若是 c 不爲空,就會 c 中的元素拷貝到 elementData 中。post
擴容的過程能夠用如下的流程圖來表示:spa
擴容對 ArrayList 來講是一個很重要的過程,這也是爲何它比數組好用的緣由。
ArrayList 的擴容有兩種方式,一個是自動擴容,一種是手動擴容。自動擴容每次會把當前容器的大小擴大 1.5 倍,手動擴容須要指定大小。既然已經有了自動擴容,那爲何還須要手動擴容呢?設想一個場景,實例化一個 ArrayList 以後,你大概知道會填充一萬個元素,若是這個時候自動擴容的話要通過屢次擴容才能裝下這麼多元素,可是手動指定容器大小的話只須要一次就能夠了。
具體把 ArrayList 擴容到多大是由下面這段代碼決定的:
private int newCapacity(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(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;
}
複製代碼
newCapacity
是根據當前的元素的個數計算出來的,右移一位表明除以2,因此 newCapacity 爲當前容量的 1.5 倍。而後這個值會與傳入的值 minCapacity
進行對比,兩個值哪一個大就用哪一個。
爲何每次自動擴容都能爲當前大小的 1.5 倍呢?那是由於自動擴容的時候傳入的 minCapacity 都只比當前的容量大 1,因此確定小於 newCapacity。而 newCapacity 就是 當前容量大小的 1.5 倍。
固然有一個狀況例外,那就是若是在實例化 ArrayList 沒有指定大小的話,ArrayList 會至少擴容到 10。這一機制是靠如下代碼實現的:
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
複製代碼
擴容的時候,都是使用 Arrays.copyOf
將元素拷貝到新的容器中。因此基本都是 的時間複雜度,代價很大。因此儘量減小擴容的次數。
注意:ArrayList 沒有縮容的過程。
ArrayList 中有了不少的方法,這些方法核心都是圍繞 elementData 操做的。
siez()
和 isEmpty()
方法想着簡單,一個用來返回容器中的元素的數量,一個用來判斷容器是否爲空。
clone()
、 toArray()
和 toArray(T[] a)
這三個方法本質上都是對容器當前的元素作一個備份,都用到了 Arrays.copyOf()
方法。可是須要注意的是 toArray(T[] a)
的實現:
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
複製代碼
在這個方法中除了使用 Arrays.copyOf()
還用到了 System.arraycopy()
,其實 Arrays.copyOf() 底層就是使用 System.arraycopy() 方法實現的。可是區別在於前者會返回一個一個新的數組,後者則是直接在原數組上進行操做。
ArrayList 中的 add()
、get()
、set()
、remove()
等方法用於元素的增刪改查,實現並不複雜,只是在操做元素以前須要對容器的 size 進行檢查,若是不知足操做要求,就會報出異常。
euqal()
類的方法主要都是對比每一個元素的類型、順序和值是否一致。
在 JDK1.8 之後,出現了 removeIf()
方法,這個方法使得從容器中刪除元素變得很簡單。
ArrayList 中有兩個內部類 Itr
和 ListItr
,主要方法以下:
private class Itr implements Iterator<E> {
Itr() {}
public boolean hasNext() {
}
public E next() {
}
public void remove() {
}
}
複製代碼
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
cursor = index;
}
public boolean hasPrevious() {
}
public int nextIndex() {
}
public int previousIndex() {
}
public E previous() {
}
public void set(E e) {
}
public void add(E e) {
}
}
複製代碼
ListItr 繼承了 Itr,這兩個內部類都實現了迭代器模式,用於遍歷 ArrayList 的元素。從上面的方法可知,Itr 和 ListItr 最大的區別在於 ListItr 能夠從兩個方向對容器的元素進行遍歷。而 Itr 只能使用順着一個方向進行遍歷。
在 JDK1.8 之後,ArrayList 中有一個 ArrayListSpliterator
內部類,這個類用於分割容器。用於提高多線程環境中的處理效率:
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(i);
}
Spliterator<Integer> s0 = list.spliterator();
System.out.println(s0.estimateSize()); // 5
Spliterator<Integer> s1 = s0.trySplit();
System.out.println(s1.estimateSize()); // 5
複製代碼
s0
中有 10 個元素,在調用 s0.trySplit()
方法以後,s0 和 s1 中各有 5 個元素。而後能夠對分割開的元素進行處理:
s0.forEachRemaining(i -> System.out.println(i));
複製代碼
ArrayList 中還有一個內部類 SubList
。SubList 用於返回 ArrayList 的一部分元素,內部的操做方法與 ArrayList 基本一致,可是須要注意的是,對 SubList 的操做會直接影響到原 ArrarList。
在 ArrayList 中,checkForComodification()
和 ConcurrentModificationException()
使用的頻率很高。這個和 fail-fast 機制有關。
ArrayList 不是線程安全的,因此在對容器操做的過程當中,容器的元素倍其餘的操做或者線程修改以後,就會出現 ConcurrentModificationException 異常。checkForComodification() 方法就是用來檢查元素是否被修改。這個機制就稱之爲 fail-fast
。
後續會有其餘的文章來介紹 fail-fast
。
(完)
相關文章
關注微信公衆號,聊點其餘的