JDK原始碼筆記-ArrayList
(1)構造方法
#ArrayList()
/** * 預設初始化容量 * * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10; /** * 共享的空陣列物件,用於 {@link #ArrayList()} 構造方法。 * * 通過使用該靜態變數,和 {@link #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 = {}; /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
ArrayList被初始化為DEFAULTCAPACITY_EMPTY_ELEMENTDATA
這個空陣列,ArrayList 考慮到節省記憶體,一些使用場景下僅僅是建立了 ArrayList 物件,實際並未使用。所以,ArrayList 優化成初始化是個空陣列,在首次新增元素時,才真正初始化為容量為 10 的陣列。
那麼為什麼單獨聲明瞭DEFAULTCAPACITY_EMPTY_ELEMENTDATA
空陣列,而不直接使用EMPTY_ELEMENTDATA
呢?在下文中,我們會看到DEFAULTCAPACITY_EMPTY_ELEMENTDATA
首次擴容為 10 ,而EMPTY_ELEMENTDATA
按照1.5 倍擴容從 0 開始而不是 10
#ArrayList(int initialCapacity)根據傳入的初始化容量,建立 ArrayList 陣列。如果我們在使用時,如果預先指到陣列大小,一定要使用該構造方法,可以避免陣列擴容提升效能,同時也是合理使用記憶體。
/** * 共享的空陣列物件。 * * 在 {@link #ArrayList(int)} 或 {@link #ArrayList(Collection)} 構造方法中, * 如果傳入的初始化大小或者集合大小為 0 時,將 {@link #elementData} 指向它。 * * Shared empty array instance used for empty instances. */ private static final Object[] EMPTY_ELEMENTDATA = {}; public ArrayList(int initialCapacity) { // 初始化容量大於 0 時,建立 Object 陣列 if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; // 初始化容量等於 0 時,使用 EMPTY_ELEMENTDATA 物件 } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; // 初始化容量小於 0 時,丟擲 IllegalArgumentException 異常 } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
比較特殊的是,如果初始化容量為 0 時,使用EMPTY_ELEMENTDATA
空陣列。在新增元素的時候,會進行擴容建立需要的陣列
#ArrayList(Collection<? extends E> c)使用傳入的
c
集合,作為 ArrayList 的elementData
public ArrayList(Collection<? extends E> c) { // 將 c 轉換成 Object 陣列 elementData = c.toArray(); // 如果陣列大小大於 0 if ((size = elementData.length) != 0) { // defend against c.toArray (incorrectly) not returning Object[] // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652) // <X> 如果集合元素不是 Object[] 型別,則會建立新的 Object[] 陣列,並將 elementData 賦值到其中,最後賦值給 elementData 。 if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); // 如果陣列大小等於 0 ,則使用 EMPTY_ELEMENTDATA 。 } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
(2)新增單個元素
#add(E e)
方法,順序新增單個元素到陣列
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! //設定到末尾並賦值 elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private static int calculateCapacity(Object[] elementData, int minCapacity) { //判斷是否是空參構造時的空陣列,如果是則確保最新有最小容量10 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } private void ensureExplicitCapacity(int minCapacity) { //增加陣列修改次數modCount,
在父類 AbstractList 上,定義了modCount
屬性,用於記錄陣列修改次數。 modCount++; // overflow-conscious code //如果新增一個元素後,容量大於elementData則擴容 if (minCapacity - elementData.length > 0) //擴容待會單獨拿出來分析 grow(minCapacity); }
#add(int index, E element),插入單個元素到指定位置
public void add(int index, E element) { //校驗是否在陣列範圍內 rangeCheckForAdd(index); //校驗是否擴容 ensureCapacityInternal(size + 1); // Increments modCount!! //將index位置開始的元素往後挪 System.arraycopy(elementData, index, elementData, index + 1, size - index); //插入到指定位置 elementData[index] = element; size++; } private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
(3)陣列擴容
#grow()
方法,擴容陣列,並返回它。整個的擴容過程,首先建立一個新的更大的陣列,一般是1.5 倍大小(為什麼說是一般呢,稍後我們會看到,會有一些小細節),然後將原陣列複製到新陣列中,最後返回新陣列。
private void grow(int minCapacity) { // 舊陣列容量 int oldCapacity = elementData.length; //新陣列容量 1.5倍 //oldCapacity >> 1 向右位運算 例如:4 二進位制 100 >> 1 = 10 變成 2 int newCapacity = oldCapacity + (oldCapacity >> 1); //確保擴容最小為1.5倍 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //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); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); //四捨五入,保證不超過int的最大值 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
(4)新增多個元素
#addAll(Collection<? extends E> c)
方法,批量新增多個元素。在我們明確知道會新增多個元素時,推薦使用該該方法而不是新增單個元素,避免可能多次擴容
public boolean addAll(Collection<? extends E> c) { //將c轉成陣列 Object[] a = c.toArray(); //c的長度 int numNew = a.length; //校驗新增這些元素需不需要擴容,保證擴容最小1.5倍 ensureCapacityInternal(size + numNew); // Increments modCount //將c中的元素複製到elementData從size位置開始 System.arraycopy(a, 0, elementData, size, numNew); //陣列大小增加c的長度 size += numNew; return numNew != 0; }
(5)從指定位置插入多個元素
#addAll(int index, Collection<? extends E> c)
方法,從指定位置開始插入多個元素
public boolean addAll(int index, Collection<? extends E> c) { //校驗插入位置是否在陣列內 rangeCheckForAdd(index); //將想要新增的元素轉為陣列 Object[] a = c.toArray(); //拿到想要新增元素的長度 int numNew = a.length; //校驗是否需要擴容,保證最小擴容1.5倍 ensureCapacityInternal(size + numNew); // Increments modCount //拿到插入位置後的elementData中元素的長度 int numMoved = size - index; //插入位置不是在elementData末尾 if (numMoved > 0) //複製,將插入位置後的元素,全部後挪到能夠容納插入新元素的位置 System.arraycopy(elementData, index, elementData, index + numNew, numMoved); //將插入的新元素複製進指定位置 System.arraycopy(a, 0, elementData, index, numNew); //增加陣列長度 size += numNew; return numNew != 0; }
(6)移除單個元素
#remove(int index)
方法,移除指定位置的元素,並返回該位置的原元素
public E remove(int index) { //檢查插入位置是不是在陣列內 rangeCheck(index); //運算元+1 modCount++; //拿到elementData中索引位置的元素 E oldValue = elementData(index); //拿到索引位置後元素的數量,這裡注意size是從1開始的,index是從0開始的 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; }
#remove(Object o)
方法,移除首個為o
的元素,並返回是否移除到
public boolean remove(Object o) { //如果刪除元素是null if (o == null) { //迴圈遍歷 for (int index = 0; index < size; index++) //如果等於null 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 void fastRemove(int index) { //運算元+1 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 }
(7)移除多個元素
#removeRange(int fromIndex, int toIndex)
方法,批量移除[fromIndex, toIndex)
的多個元素,注意不包括toIndex
的元素
protected void removeRange(int fromIndex, int toIndex) { //運算元+1 modCount++; //toIndex後的元素數量,也就是需要移動的元素數量 int numMoved = size - toIndex; //toIndex後的元素前挪 System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved); // clear to let GC do its work //元素前挪後,將後邊的位置置null int newSize = size - (toIndex-fromIndex); for (int i = newSize; i < size; i++) { elementData[i] = null; } size = newSize; }
(8)批量移除指定的多個元素
#removeAll(Collection<?> c)
方法,移除在c中的元素
public boolean removeAll(Collection<?> c) { //非空判斷 Objects.requireNonNull(c); return batchRemove(c, false); } private boolean batchRemove(Collection<?> c, boolean complement) { final Object[] elementData = this.elementData; //需要理解r和w的意思,r其實是遍歷elementData的索引,w是elementData中的c有的元素,就是需要刪除的元素的索引 int r = 0, w = 0; boolean modified = false; try { //迴圈遍歷原陣列 for (; r < size; r++) //如果elementData當前元素c中有,那麼覆蓋w位置的元素 if (c.contains(elementData[r]) == complement) elementData[w++] = elementData[r]; } finally { // Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. //異常處理 將r位置之後的的元素寫入到w位置之後,保證不多出來 if (r != size) { System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } //因為在之前就將需要剩下的元素前挪了,所以將w位置之後的元素置null if (w != size) { // clear to let GC do its work for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; modified = true; } } return modified; }
#retainAll(Collection<?> c)
求elementData
陣列和指定多個元素的交集,刪除不在c中的元素,因為邏輯與removeRange相同,所以略過
(9)查詢單個元素
#indexOf(Object o)
方法,查詢首個為指定元素的位置
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; }
#contains(Object o)
方法,就是基於該方法實現
public boolean contains(Object o) { return indexOf(o) >= 0; }
有時我們需要查詢最後一個為指定元素的位置,所以會使用到#lastIndexOf(Object o)
方法
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; }
(10)獲取指定位置的元素
#get(int index)
方法,獲得指定位置的元素
public E get(int index) { //校驗索引是否越界 rangeCheck(index); return elementData(index); } E elementData(int index) { //返回指定索引位置的元素 return (E) elementData[index]; }
(11)設定指定位置的元素
#set(int index, E element)
方法,設定指定位置的元素
public E set(int index, E element) { //越界校驗 rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
(12)轉換成陣列
#toArray()
方法,將 ArrayList 轉換成[]
陣列
public Object[] toArray() { //注意返回的是Object型別的陣列 return Arrays.copyOf(elementData, size); }
實際場景下,我們可能想要指定T
泛型的陣列,那麼我們就需要使用到#toArray(T[] a)
方法
public <T> T[] toArray(T[] a) { //如果a比elementData小,直接複製一個數組返回 if (a.length < size) // Make a new array of a's runtime type, but my contents: return (T[]) Arrays.copyOf(elementData, size, a.getClass()); //如果elementData比a小,則將elementData複製到a中 System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; }
(13)判斷相等
#equals(Object o)
方法,判斷是否相等
public boolean equals(Object o) { // 如果是自己,直接返回相等 if (o == this) { return true; } // 如果不為 List 型別,直接不相等 if (!(o instanceof List)) { return false; } // 獲得當前的陣列修改次數 final int expectedModCount = modCount; // ArrayList can be subclassed and given arbitrary behavior, but we can // still deal with the common case where o is ArrayList precisely // <X> 根據不同型別,呼叫不同比對的方法。主要考慮 ArrayList 可以直接使用其 elementData 屬性,效能更優。 boolean equal = (o.getClass() == ArrayList.class) ? equalsArrayList((ArrayList<?>) o) : equalsRange((List<?>) o, 0, size); // 如果修改次數發生改變,則丟擲 ConcurrentModificationException 異常 checkForComodification(expectedModCount); return equal; }
選擇兩種比較方法的原因:普通的list只能使用迭代器,相比與ArrayList的elementData遍歷,效能會略低一些
boolean equalsRange(List<?> other, int from, int to) { // 如果 to 大於 es 大小,說明說明發生改變,丟擲 ConcurrentModificationException 異常 final Object[] es = elementData; if (to > es.length) { throw new ConcurrentModificationException(); } // 通過迭代器遍歷 other ,然後逐個元素對比 var oit = other.iterator(); for (; from < to; from++) { // 如果 oit 沒有下一個,或者元素不相等,返回 false 不匹配 if (!oit.hasNext() || !Objects.equals(es[from], oit.next())) { return false; } } // 通過 oit 是否遍歷完。實現大小是否相等的效果。 return !oit.hasNext(); } private boolean equalsArrayList(ArrayList<?> other) { // 獲得 other 陣列修改次數 final int otherModCount = other.modCount; final int s = size; boolean equal; // 判斷陣列大小是否相等 if (equal = (s == other.size)) { final Object[] otherEs = other.elementData; final Object[] es = elementData; // 如果 s 大於 es 或者 otherEs 的長度,說明發生改變,丟擲 ConcurrentModificationException 異常 if (s > es.length || s > otherEs.length) { throw new ConcurrentModificationException(); } // 遍歷,逐個比較每個元素是否相等 for (int i = 0; i < s; i++) { if (!Objects.equals(es[i], otherEs[i])) { equal = false; break; // 如果不相等,則 break } } } // 如果 other 修改次數發生改變,則丟擲 ConcurrentModificationException 異常 other.checkForComodification(otherModCount); return equal; }
(14)清空陣列
public void clear() { modCount++; // clear to let GC do its work //遍歷置null for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
(15)克隆
#clone()
方法,克隆 ArrayList 物件
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); } }
(16)建立子陣列
#subList(int fromIndex, int toIndex)
方法,建立 ArrayList 的子陣列
public List<E> subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size); return new SubList(this, 0, fromIndex, toIndex); }
程式碼並不複雜,需要注意的是,SubList 不是一個只讀陣列,而是和根陣列root
共享相同的elementData
陣列,只是說限制了[fromIndex, toIndex)
的範圍
(17)建立Iterator迭代器
#iterator()
方法,建立迭代器。一般情況下,我們使用迭代器遍歷 ArrayList、LinkedList 等等 List 的實現類
public Iterator<E> iterator() { return new Itr(); }
建立 Itr 迭代器。Itr 實現java.util.Iterator
介面,是 ArrayList 的內部類。雖然說 AbstractList 也提供了一個 Itr 的實現,但是 ArrayList 為了更好的效能,所以自己實現了,在其類上也有註釋“An optimized version of AbstractList.Itr”
Itr一共有三個屬性:
/** * 下一個訪問元素的位置,從下標 0 開始。 */ int cursor; // index of next element to return /** * 上一次訪問元素的位置。 * * 1. 初始化為 -1 ,表示無上一個訪問的元素 * 2. 遍歷到下一個元素時,lastRet 會指向當前元素,而 cursor 會指向下一個元素。這樣,如果我們要實現 remove 方法,移除當前元素,就可以實現了。 * 3. 移除元素時,設定為 -1 ,表示最後訪問的元素不存在了,都被移除咧。 */ int lastRet = -1; // index of last element returned; -1 if no such /** * 建立迭代器時,陣列修改次數。 * * 在迭代過程中,如果陣列發生了變化,會丟擲 ConcurrentModificationException 異常。 */ int expectedModCount = modCount; // prevent creating a synthetic constructor Itr() {}
Itr對Iterator的四個實現方法:
#hasNext()
方法,判斷是否還可以繼續迭代
public boolean hasNext() { //cursor = size,說明到頭了 return cursor != size; }
#next()
方法,下一個元素
public E next() { //檢查運算元,判斷陣列是否發生了變化 checkForComodification(); //超出size,丟擲異常 //i記錄當前cursor位置 int i = cursor; if (i >= size) throw new NoSuchElementException(); //判斷如果超出elementData大小,則說明被修改,丟擲異常 Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); //cursor指向下一個位置 cursor = i + 1; //返回當前位置的元素,且lastRet指向當前位置 return (E) elementData[lastRet = i]; }
#remove()
方法,移除當前元素
public void remove() { //lastRet小於0,則沒有指向任何元素,丟擲異常 if (lastRet < 0) throw new IllegalStateException(); //檢查陣列是否發生變化 checkForComodification(); try { //移除lastRet位置的元素 ArrayList.this.remove(lastRet); //cursor指向lastRet位置,因為被移除了,需要後退 cursor = lastRet; //因為元素被移除,則lastRet不指向任何位置 lastRet = -1; //記錄新的陣列修改數 expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }
#forEachRemaining(Consumer<? super E> action)
方法,消費剩餘未迭代的元素
public void forEachRemaining(Consumer<? super E> consumer) { //判斷是否為null,為null丟擲異常 Objects.requireNonNull(consumer); //獲取當前陣列大小 final int size = ArrayList.this.size; //將i指向cursor int i = cursor; //如果大於size,則沒有需要消費的元素 if (i >= size) { return; } //拿到當前陣列 final Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } //逐個處理 while (i != size && modCount == expectedModCount) { consumer.accept((E) elementData[i++]); } // update once at end of iteration to reduce heap write traffic //更新cursor、lastRet的指向 cursor = i; lastRet = i - 1; checkForComodification(); }