ArrayList 擴容機制
1. ArrayList 簡介
ArrayList
的底層是陣列佇列,相當於動態陣列。與 Java 中的陣列相比,它的容量能動態增長。在新增大量元素前,應用程式可以使用ensureCapacity
操作來增加 ArrayList
例項的容量。這可以減少遞增式再分配的數量。
ArrayList
繼承於 AbstractList
,實現了 List
, RandomAccess
, Cloneable
, java.io.Serializable
這些介面。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ }
RandomAccess
是一個標誌介面,表明實現這個這個介面的 List 集合是支援快速隨機訪問的。在ArrayList
中,我們即可以通過元素的序號快速獲取元素物件,這就是快速隨機訪問。ArrayList
實現了Cloneable
介面 ,即覆蓋了函式clone()
,能被克隆。ArrayList
實現了java.io.Serializable
介面,這意味著ArrayList
支援序列化,能通過序列化去傳輸。
1.1. Arraylist 和 Vector 的區別?
ArrayList
是List
的主要實現類,底層使用Object[ ]
儲存,適用於頻繁的查詢工作,執行緒不安全 ;Vector
是List
的古老實現類,底層使用Object[ ]
儲存,執行緒安全的。
1.2. Arraylist 與 LinkedList 區別?
- 是否保證執行緒安全:
ArrayList
和LinkedList
都是不同步的,也就是不保證執行緒安全; - 底層資料結構:
Arraylist
底層使用的是Object
陣列;LinkedList
底層使用的是 雙向連結串列 資料結構(JDK1.6 之前為迴圈連結串列,JDK1.7 取消了迴圈。注意雙向連結串列和雙向迴圈連結串列的區別,下面有介紹到!) - 插入和刪除是否受元素位置的影響: ①
ArrayList
採用陣列儲存,所以插入和刪除元素的時間複雜度受元素位置的影響。 比如:執行add(E e)
方法的時候,ArrayList
會預設在將指定的元素追加到此列表的末尾,這種情況時間複雜度就是 O(1)。但是如果要在指定位置 i 插入和刪除元素的話(add(int index, E element)
)時間複雜度就為 O(n-i)。因為在進行上述操作的時候集合中第 i 和第 i 個元素之後的(n-i)個元素都要執行向後位/向前移一位的操作。 ②LinkedList
採用連結串列儲存,所以對於add(E e)
方法的插入,刪除元素時間複雜度不受元素位置的影響,近似 O(1),如果是要在指定位置i
插入和刪除元素的話((add(int index, E element)
) 時間複雜度近似為o(n))
因為需要先移動到指定位置再插入。 - 是否支援快速隨機訪問:
LinkedList
不支援高效的隨機元素訪問,而ArrayList
支援。快速隨機訪問就是通過元素的序號快速獲取元素物件(對應於get(int index)
方法)。 - 記憶體空間佔用:
ArrayList
的空 間浪費主要體現在在 list 列表的結尾會預留一定的容量空間,而LinkedList
的空間花費則體現在它的每一個元素都需要消耗比ArrayList
更多的空間(因為要存放直接後繼和直接前驅以及資料)。
2. ArrayList 核心原始碼
package java.util; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; /** * 預設初始容量大小 */ private static final int DEFAULT_CAPACITY = 10; /** * 空陣列(用於空例項)。 */ private static final Object[] EMPTY_ELEMENTDATA = {}; //用於預設大小空例項的共享空陣列例項。 //我們把它從EMPTY_ELEMENTDATA陣列中區分出來,以知道在新增第一個元素時容量需要增加多少。 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 儲存ArrayList資料的陣列 */ transient Object[] elementData; // non-private to simplify nested class access /** * ArrayList 所包含的元素個數 */ private int size; /** * 帶初始容量引數的建構函式(使用者可以在建立ArrayList物件時自己指定集合的初始大小) */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { //如果傳入的引數大於0,建立initialCapacity大小的陣列 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { //如果傳入的引數等於0,建立空陣列 this.elementData = EMPTY_ELEMENTDATA; } else { //其他情況,丟擲異常 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** *預設無參建構函式 *DEFAULTCAPACITY_EMPTY_ELEMENTDATA 為0.初始化為10,也就是說初始其實是空陣列 當新增第一個元素的時候陣列容量才變成10 */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * 構造一個包含指定集合的元素的列表,按照它們由集合的迭代器返回的順序。 */ public ArrayList(Collection<? extends E> c) { //將指定集合轉換為陣列 elementData = c.toArray(); //如果elementData陣列的長度不為0 if ((size = elementData.length) != 0) { // 如果elementData不是Object型別資料(c.toArray可能返回的不是Object型別的陣列所以加上下面的語句用於判斷) if (elementData.getClass() != Object[].class) //將原來不是Object型別的elementData陣列的內容,賦值給新的Object型別的elementData陣列 elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // 其他情況,用空陣列代替 this.elementData = EMPTY_ELEMENTDATA; } } /** * 修改這個ArrayList例項的容量是列表的當前大小。 應用程式可以使用此操作來最小化ArrayList例項的儲存。 */ public void trimToSize() { modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } } //下面是ArrayList的擴容機制 //ArrayList的擴容機制提高了效能,如果每次只擴充一個, //那麼頻繁的插入會導致頻繁的拷貝,降低效能,而ArrayList的擴容機制避免了這種情況。 /** * 如有必要,增加此ArrayList例項的容量,以確保它至少能容納元素的數量 * @param minCapacity 所需的最小容量 */ public void ensureCapacity(int minCapacity) { //如果是true,minExpand的值為0,如果是false,minExpand的值為10 int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // any size if not default element table ? 0 // larger than default for default empty table. It's already // supposed to be at default size. : DEFAULT_CAPACITY; //如果最小容量大於已有的最大容量 if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } } //得到最小擴容量 private void ensureCapacityInternal(int minCapacity) { 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方法進行擴容,呼叫此方法代表已經開始擴容了 grow(minCapacity); } /** * 要分配的最大陣列大小 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * ArrayList擴容的核心方法。 */ private void grow(int minCapacity) { // oldCapacity為舊容量,newCapacity為新容量 int oldCapacity = elementData.length; //將oldCapacity 右移一位,其效果相當於oldCapacity /2, //我們知道位運算的速度遠遠快於整除運算,整句運算式的結果就是將新容量更新為舊容量的1.5倍, int newCapacity = oldCapacity + (oldCapacity >> 1); //然後檢查新容量是否大於最小需要容量,若還是小於最小需要容量,那麼就把最小需要容量當作陣列的新容量, if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //再檢查新容量是否超出了ArrayList所定義的最大容量, //若超出了,則呼叫hugeCapacity()來比較minCapacity和 MAX_ARRAY_SIZE, //如果minCapacity大於MAX_ARRAY_SIZE,則新容量則為Interger.MAX_VALUE,否則,新容量大小則為 MAX_ARRAY_SIZE。 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); } //比較minCapacity和 MAX_ARRAY_SIZE private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } /** *返回此列表中的元素數。 */ public int size() { return size; } /** * 如果此列表不包含元素,則返回 true 。 */ public boolean isEmpty() { //注意=和==的區別 return size == 0; } /** * 如果此列表包含指定的元素,則返回true 。 */ public boolean contains(Object o) { //indexOf()方法:返回此列表中指定元素的首次出現的索引,如果此列表不包含此元素,則為-1 return indexOf(o) >= 0; } /** *返回此列表中指定元素的首次出現的索引,如果此列表不包含此元素,則為-1 */ 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++) //equals()方法比較 if (o.equals(elementData[i])) return i; } return -1; } /** * 返回此列表中指定元素的最後一次出現的索引,如果此列表不包含元素,則返回-1。. */ 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; } /** * 返回此ArrayList例項的淺拷貝。 (元素本身不被複制。) */ public Object clone() { try { ArrayList<?> v = (ArrayList<?>) super.clone(); //Arrays.copyOf功能是實現陣列的複製,返回複製後的陣列。引數是被複制的陣列和複製的長度 v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // 這不應該發生,因為我們是可以克隆的 throw new InternalError(e); } } /** *以正確的順序(從第一個到最後一個元素)返回一個包含此列表中所有元素的陣列。 *返回的陣列將是“安全的”,因為該列表不保留對它的引用。 (換句話說,這個方法必須分配一個新的陣列)。 *因此,呼叫者可以自由地修改返回的陣列。 此方法充當基於陣列和基於集合的API之間的橋樑。 */ public Object[] toArray() { return Arrays.copyOf(elementData, size); } /** * 以正確的順序返回一個包含此列表中所有元素的陣列(從第一個到最後一個元素); *返回的陣列的執行時型別是指定陣列的執行時型別。 如果列表適合指定的陣列,則返回其中。 *否則,將為指定陣列的執行時型別和此列表的大小分配一個新陣列。 *如果列表適用於指定的陣列,其餘空間(即陣列的列表數量多於此元素),則緊跟在集合結束後的陣列中的元素設定為null 。 *(這僅在呼叫者知道列表不包含任何空元素的情況下才能確定列表的長度。) */ @SuppressWarnings("unchecked") public <T> T[] toArray(T[] a) { if (a.length < size) // 新建一個執行時型別的陣列,但是ArrayList陣列的內容 return (T[]) Arrays.copyOf(elementData, size, a.getClass()); //呼叫System提供的arraycopy()方法實現陣列之間的複製 System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; } // Positional Access Operations @SuppressWarnings("unchecked") E elementData(int index) { return (E) elementData[index]; } /** * 返回此列表中指定位置的元素。 */ public E get(int index) { rangeCheck(index); return elementData(index); } /** * 用指定的元素替換此列表中指定位置的元素。 */ public E set(int index, E element) { //對index進行界限檢查 rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; //返回原來在這個位置的元素 return oldValue; } /** * 將指定的元素追加到此列表的末尾。 */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! //這裡看到ArrayList新增元素的實質就相當於為陣列賦值 elementData[size++] = e; return true; } /** * 在此列表中的指定位置插入指定的元素。 *先呼叫 rangeCheckForAdd 對index進行界限檢查;然後呼叫 ensureCapacityInternal 方法保證capacity足夠大; *再將從index開始之後的所有成員後移一個位置;將element插入index位置;最後size加1。 */ public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! //arraycopy()這個實現陣列之間複製的方法一定要看一下,下面就用到了arraycopy()方法實現陣列自己複製自己 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } /** * 刪除該列表中指定位置的元素。 將任何後續元素移動到左側(從其索引中減去一個元素)。 */ public E remove(int index) { rangeCheck(index); modCount++; E oldValue = 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; } /** * 從列表中刪除指定元素的第一個出現(如果存在)。 如果列表不包含該元素,則它不會更改。 *返回true,如果此列表包含指定的元素 */ 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 remove method that skips bounds checking and does not * return the value removed. */ 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 } /** * 從列表中刪除所有元素。 */ public void clear() { modCount++; // 把陣列中所有的元素的值設為null for (int i = 0; i < size; i++) elementData[i] = null; size = 0; } /** * 按指定集合的Iterator返回的順序將指定集合中的所有元素追加到此列表的末尾。 */ 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; } /** * 將指定集合中的所有元素插入到此列表中,從指定的位置開始。 */ 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; } /** * 從此列表中刪除所有索引為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; } /** * 檢查給定的索引是否在範圍內。 */ private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } /** * add和addAll使用的rangeCheck的一個版本 */ private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } /** * 返回IndexOutOfBoundsException細節資訊 */ private String outOfBoundsMsg(int index) { return "Index: "+index+", Size: "+size; } /** * 從此列表中刪除指定集合中包含的所有元素。 */ public boolean removeAll(Collection<?> c) { Objects.requireNonNull(c); //如果此列表被修改則返回true return batchRemove(c, false); } /** * 僅保留此列表中包含在指定集合中的元素。 *換句話說,從此列表中刪除其中不包含在指定集合中的所有元素。 */ public boolean retainAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, true); } /** * 從列表中的指定位置開始,返回列表中的元素(按正確順序)的列表迭代器。 *指定的索引表示初始呼叫將返回的第一個元素為next 。 初始呼叫previous將返回指定索引減1的元素。 *返回的列表迭代器是fail-fast 。 */ public ListIterator<E> listIterator(int index) { if (index < 0 || index > size) throw new IndexOutOfBoundsException("Index: "+index); return new ListItr(index); } /** *返回列表中的列表迭代器(按適當的順序)。 *返回的列表迭代器是fail-fast 。 */ public ListIterator<E> listIterator() { return new ListItr(0); } /** *以正確的順序返回該列表中的元素的迭代器。 *返回的迭代器是fail-fast 。 */ public Iterator<E> iterator() { return new Itr(); }
3. ArrayList 擴容機制分析
3.1. 先從 ArrayList 的建構函式說起
(JDK8)ArrayList 有三種方式來初始化,構造方法原始碼如下:
/** * 預設初始容量大小 */ private static final int DEFAULT_CAPACITY = 10; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** *預設建構函式,使用初始容量10構造一個空列表(無引數構造) */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * 帶初始容量引數的建構函式。(使用者自己指定容量) */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) {//初始容量大於0 //建立initialCapacity大小的陣列 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) {//初始容量等於0 //建立空陣列 this.elementData = EMPTY_ELEMENTDATA; } else {//初始容量小於0,丟擲異常 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** *構造包含指定collection元素的列表,這些元素利用該集合的迭代器按順序返回 *如果指定的集合為null,throws NullPointerException。 */ 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; } }
注意:以無引數構造方法建立 ArrayList
時,實際上初始化賦值的是一個空陣列。當真正對陣列進行新增元素操作時,才真正分配容量。即向陣列中新增第一個元素時,陣列容量擴為 10。 下面在我們分析 ArrayList 擴容時會講到這一點內容!
3.2. 分析 ArrayList 擴容機制
/** * 將指定的元素追加到此列表的末尾。 */ public boolean add(E e) { //新增元素之前,先呼叫ensureCapacityInternal方法 ensureCapacityInternal(size + 1); // Increments modCount!! //這裡看到ArrayList新增元素的實質就相當於為陣列賦值 elementData[size++] = e; return true; }
3.2.2. ensureCapacityInternal()
方法
//得到最小擴容量 private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 獲取預設的容量和傳入引數的較大值 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); }
當 要 add 進第 1 個元素時,minCapacity 為 1,在 Math.max()方法比較後,minCapacity 為 10。
3.2.3. ensureExplicitCapacity()
方法
如果呼叫 ensureCapacityInternal()
方法就一定會進入(執行)這個方法,下面我們來研究一下這個方法的原始碼!
//判斷是否需要擴容 private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) //呼叫grow方法進行擴容,呼叫此方法代表已經開始擴容了 grow(minCapacity); }
- 當我們要 add 進第 1 個元素到 ArrayList 時,elementData.length 為 0 (因為還是一個空的 list),因為執行了
ensureCapacityInternal()
方法 ,所以 minCapacity 此時為 10。此時,minCapacity - elementData.length > 0
成立,所以會進入grow(minCapacity)
方法。 - 當 add 第 2 個元素時,minCapacity 為 2,此時 e lementData.length(容量)在新增第一個元素後擴容成 10 了。此時,
minCapacity - elementData.length > 0
不成立,所以不會進入 (執行)grow(minCapacity)
方法。 - 新增第 3、4···到第 10 個元素時,依然不會執行 grow 方法,陣列容量都為 10。
直到新增第 11 個元素,minCapacity(為 11)比 elementData.length(為 10)要大。進入 grow 方法進行擴容。
3.2.4. grow()
方法
/** * 要分配的最大陣列大小 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * ArrayList擴容的核心方法。 */ private void grow(int minCapacity) { // oldCapacity為舊容量,newCapacity為新容量 int oldCapacity = elementData.length; //將oldCapacity 右移一位,其效果相當於oldCapacity /2, //我們知道位運算的速度遠遠快於整除運算,整句運算式的結果就是將新容量更新為舊容量的1.5倍, int newCapacity = oldCapacity + (oldCapacity >> 1); //然後檢查新容量是否大於最小需要容量,若還是小於最小需要容量,那麼就把最小需要容量當作陣列的新容量, if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 如果新容量大於 MAX_ARRAY_SIZE,進入(執行) `hugeCapacity()` 方法來比較 minCapacity 和 MAX_ARRAY_SIZE, //如果minCapacity大於最大容量,則新容量則為`Integer.MAX_VALUE`,否則,新容量大小則為 MAX_ARRAY_SIZE 即為 `Integer.MAX_VALUE - 8`。 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); }
3.2.5. hugeCapacity()
方法。
從上面 grow()
方法原始碼我們知道: 如果新容量大於 MAX_ARRAY_SIZE,進入(執行) hugeCapacity()
方法來比較 minCapacity 和 MAX_ARRAY_SIZE,如果 minCapacity 大於最大容量,則新容量則為Integer.MAX_VALUE
,否則,新容量大小則為 MAX_ARRAY_SIZE 即為 Integer.MAX_VALUE - 8
。
private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); //對minCapacity和MAX_ARRAY_SIZE進行比較 //若minCapacity大,將Integer.MAX_VALUE作為新陣列的大小 //若MAX_ARRAY_SIZE大,將MAX_ARRAY_SIZE作為新陣列的大小 //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }