1. 程式人生 > >深入淺出!阿里P7架構師帶你分析ArrayList集合原始碼,建議是先收藏再看!

深入淺出!阿里P7架構師帶你分析ArrayList集合原始碼,建議是先收藏再看!

# ArrayList簡介 ArrayList 是 Java 集合框架中比較常用的資料結構了。ArrayList是可以**動態增長和縮減的索引序列**,內部封裝了一個**動態再分配的Object[]陣列** ![](https://upload-images.jianshu.io/upload_images/23140115-cf34845ac6a8e5ce?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 這裡我們可以看到ArrayList繼承抽象類AbstractList,實現了 List 介面,同時還實現了 RandomAccess、Cloneable、Serializable 介面,所以ArrayList 是支援快速訪問、複製、序列化的。 # 主要成員變數 ``` // 底層儲存元素的陣列 transient Object[] elementData; // ArrayList的實際大小 private int size; ``` **注意:size 才是 elementData陣列中實際的元素個數,而 elementData.length 為陣列的容量,表示最多可以容納多少個元素。** ``` // ArrayList的預設初始化容量 private static final int DEFAULT_CAPACITY = 10; ``` ArrayList的預設初始容量大小為 **10**。 ``` // 記錄對List操作的次數 protected transient int modCount = 0; ``` 這個變數是定義在 AbstractList 中的,主要使用是在 Iterator,目的是防止在List在**迭代的過程中被修改**。 ``` // 空的Object型別陣列 private static final Object[] EMPTY_ELEMENTDATA = {}; // 空的Object型別陣列 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; ``` 兩個空的陣列有什麼區別呢?簡單來講就是**第一次新增元素(使用add方法)**時知道elementData是從空的建構函式還是有參建構函式被初始化的。以便確認**下一步的擴容機制**。 # 建構函式 ArrayList類共有三種建構函式: * 無參建構函式 * 帶有引數為初始容量initialCapacity的建構函式 * 帶有引數為Collection集合的建構函式 ## 1、無參建構函式 ``` public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } ``` 注意:雖然在原始碼的註釋中說該建構函式構造一個容量大小為 10 的空的ArrayList,**但實際上建構函式只是給 elementData 賦值了一個空的陣列(DEFAULTCAPACITY_EMPTY_ELEMENTDATA)**,在第一次向ArrayList新增元素時容量才擴大至10的。 ## 2、帶有引數為初始容量initialCapacity的建構函式 ``` public ArrayList(int initialCapacity) { // 如果initalCapacity大於0,直接建立一個長度Object陣列賦值為elementData; if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } // 如果initalCapcity等於0,直接將空陣列EMPETY_ELEMENTDATA複製給elementData else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } // 如果initalCapcity小於於0,則丟擲異常IllegalArgumentException else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } ``` 當 initialCapacity 為0時則是把 **EMPTY_ELEMENTDATA** 賦值給 elementData。 當 initialCapacity 大於零時初始化一個大小為 initialCapacity 的 object 陣列並賦值給 elementData。 ## 3、帶有引數為Collection集合的建構函式 ``` public ArrayList(Collection c) { // 將 Collection 轉化為陣列並賦值給elementData elementData = c.toArray(); // 把elementData中元素的個數賦值給size並判斷其是否為0 if ((size = elementData.length) != 0) { // 如果 size 不為零,則判斷 elementData 的 class 型別是否為 Object[],不是的話則做一次轉換。 if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } // 如果 size 為零,則把 EMPTY_ELEMENTDATA 賦值給 elementData,相當於new ArrayList(0)。 else { this.elementData = EMPTY_ELEMENTDATA; } } ``` 該構造方法主要就是將Collection集合的實現類轉換為ArrayList。 # 主要操作方法 ## add方法(新增單個元素) ``` public boolean add(E e) { // 先確認ArrayList集合容量大小 ensureCapacityInternal(size + 1); // 先給elementData中size位置賦值為e,然後size自增 elementData[size++] = e; return true; } ``` ``` private void ensureCapacityInternal(int minCapacity) { // 如果elementData為預設的空陣列,則給minCapacity賦值為初始的預設容量 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } // modCount自增,並確定容量大於陣列的長度 ensureExplicitCapacity(minCapacity); } ``` ``` private void ensureExplicitCapacity(int minCapacity) { // modCount自增,修改次數加1 modCount++; // 如果minCapacity超過了陣列長度,則進行擴容 if (minCapacity - elementData.length > 0) grow(minCapacity); } ``` 上述三個函式的呼叫關係很簡單,也很清楚。 * 在add方法中,每次新增元素到ArrayList中時都會先確認下集合容量大小,然後將size位置的元素賦值為e,size再進行自增。 * 在ensureCapacityInternal方法中先對elementData進行判斷 ,如果elementData為 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 就取 DEFAULT_CAPACITY 和 minCapacity 的最大值也就是10。這就是 EMPTY_ELEMENTDATA 與 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的區別所在。同時也驗證了上面的說法:**使用無參建構函式時是在第一次新增元素時初始化容量為10** 。 * ensureExplicitCapacity 方法中首先對**modCount 自增1**,記錄操作次數,然後**如果 minCapacity 大於 elementData 的長度,則對集合進行擴容**。顯然當第一次新增元素時 elementData 的長度為零。那我們來看看 grow 函式。 ``` private void grow(int minCapacity) { // ArrayList的舊容量為陣列長度 int oldCapacity = elementData.length; // 將新容量賦值為原容量的1.5倍(左移一位相當於除以二) int newCapacity = oldCapacity + (oldCapacity >> 1); // 如果此時新容量還是小於新增元素後的容量,則將新容量直接賦值為新增元素後的容量 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 如果此時新容量大於陣列的最大大小,則返回上限最大的容量 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 把舊的陣列elementData拷貝到新的elementData,並將容量設定為newCapacity elementData = Arrays.copyOf(elementData, newCapacity); } ``` 預設**將list的容量擴容至原來容量的 1.5 倍**。但是擴容之後也不一定適用,有可能太小,有可能太大。所以才會有下面兩個 if 判斷。**如果1.5倍太小的話,則將增加元素的容量大小賦值給newCapacity**,**如果1.5倍太大或者我們需要的容量太大,則呼叫hugeCapacity函式,給newCapacity賦一個合適的值。**最後將**原陣列中的資料複製到大小為 newCapacity 的新陣列中,並將新陣列賦值給 elementData**。 ``` private static int hugeCapacity(int minCapacity) { // 如果minCapacity小於0,就丟擲異常 if (minCapacity < 0) // overflow throw new OutOfMemoryError(); // 如果此時增加元素後得minCapacity大於陣列的最大長度就返回整數最大值,否則返回陣列最大值 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } ``` ## add方法(批量新增,在指定位置新增) ``` public void add(int index, E element) { // 檢查index是否越界 rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } public boolean addAll(Collection 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 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; } ``` 這三個方法基本思路與上述add方法基本思路是一致,博主這裡就不再贅述了。 ## remove方法 ``` public E remove(int index) { // 檢查index是否越界,如果越界則丟擲異常 rangeCheck(index); // modCount自增,修改次數加一 modCount++; // 獲取elementData在index位置的值 E oldValue = elementData(index); // 獲取後移的位置長度 int numMoved = size - index - 1; // 如果大於零,則呼叫System.arraycopy方法完成陣列移動 if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); // size自減,並將elementData索引值為size的元素引用賦值為空,讓GC對他進行回收 elementData[--size] = null; // 返回index位置的值 return oldValue; } ``` 當我們呼叫 remove(int index) 時,首先會**檢查 index 是否合法**,然後**再判斷要刪除的元素是否位於陣列的最後一個位置。如果 index 不是最後一個,就再次呼叫 System.arraycopy() 方法拷貝陣列**。說白了就是將從 index + 1 開始向後所有的元素都向前挪一個位置。然後將**陣列的最後一個位置空,size - 1**。如果 index 是最後一個元素那麼就直接將陣列的最後一個位置空,size - 1即可。 ``` public boolean remove(Object o) { // 如果o為空,則查詢陣列中為空的索引,並呼叫fastRemove方法進行刪除,並返回true if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } // 如果o不為空,則查詢陣列中與該元素相等的索引,並呼叫fastRemove方法進行刪除,並返回true else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } // 如果list中不存在則返回false return false; } ``` 下面我們在看fastRemove方法,fastRemove方法相較於remove(int index)方法少了一步對index的判斷,因為remove(Object o)就是在陣列中進行查詢一定是合法的,所以在fastRemove中沒必要對index進行判斷。 ``` 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 } ``` ## get方法 ``` public E get(int index) { // 檢查index是否合法,是否越界 rangeCheck(index); // 利用陣列的特點,直接訪問陣列中該索引位置上的元素 return elementData(index); } ``` # 總結 * ArrayList可以存放null。 * ArrayList本質上就是一個elementData陣列。 * ArrayList區別於陣列的地方在於能夠自動擴充套件大小,其中關鍵的方法就是gorw()方法。 * ArrayList中removeAll(collection c)和clear()的區別就是removeAll可以刪除批量指定的元素,而clear是全是刪除集合中的元素。 * ArrayList由於本質是陣列,所以它在資料的查詢方面會很快,而在插入刪除這些方面,效能下降很多,有移動很多資料才能達到應有的效果 * ArrayList實現了RandomAccess,所以在遍歷它的時候推薦使用for迴圈。 ## 最後 歡迎關注公眾號:前程有光,領取一線大廠Java面試題總結+各知識點學習思維導+一份300頁pdf文件的Java核心知識點