ArrayList原始碼解析-Java8
目錄
2.1 重要的屬性
2.2 構造方法
2.3新增元素
2.4 陣列擴容
2.5 刪除元素
2.6 陣列縮容
2.7 獲取元素
一.ArrayList介紹
ArrayList在平時開發過程中使用得特別頻繁,它的底層是使用陣列,存線上程併發安全(併發讀寫);
與之密切相關的Vector,功能和ArrayList幾乎一樣(原始碼也幾乎一樣),但是Vector是併發安全的,因為Vectory的介面,大多是加了synchronized關鍵字進行同步操作,達到併發安全的效果。
二.ArrayList原始碼分析
2.1 重要的屬性
/** * 預設容量 */ private static final int DEFAULT_CAPACITY = 10; /** * 共享的靜態變數(一個空的陣列) */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * 預設空容量的陣列 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 儲存ArrayList中元素的真實資料,使用transient表示序列化時不計入該欄位 */ transient Object[] elementData; /** * 記錄ArrayList中元素的個數 */ private int size; /** * 允許申請的最大容量 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
2.2 構造方法
ArrayList有三個構造方法,需要注意的是,使用無參構造方法來建立ArrayList時,不會建立陣列,也就是說不會分配陣列記憶體空間。程式碼如下:
public ArrayList() { // 無參構造方法,直接將儲存資料的elementData設定為空陣列 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } public ArrayList(int initialCapacity) { // 傳入的初始容量大於0,則會建立指定容量長度的陣列 if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { // 如果傳入的初始容量為0,則指定為空陣列 this.elementData = EMPTY_ELEMENTDATA; } else { // 容量小於0,屬於非法引數,直接丟擲異常 throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); } } 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 { // 如果傳入的集合為空,那麼直接設定為空的陣列 this.elementData = EMPTY_ELEMENTDATA; } }
2.3 新增新元素
新增新元素,常用的就是追加新元素和在指定位置插入新元素。
需要注意的是,ArrayList總是在真正插入元素前判斷是否需要擴容,如果需要擴容,則擴容後,再插入新元素;不需要擴容則直接插入元素。
k/** * 新增新元素(append) * * @param e 新元素 */ public boolean add(E e) { // 判斷是否需要擴容(元素數量+1 如果超過陣列容量,則會進行擴容) ensureCapacityInternal(size + 1); // 將新元素放到最後一個元素後面(同時元素數量加1) elementData[size++] = e; return true; } /** * 將新元素插入指定位置 */ public void add(int index, E element) { // 檢測index是否越界 rangeCheckForAdd(index); // 檢測是否需要擴容(若需要,則進行擴容),同時修改modCount+1 ensureCapacityInternal(size + 1); // 將index後面元素統統往後移動一個位置,空出index位置 System.arraycopy(elementData, index, elementData, index + 1, size - index); // 將新元素放入index位置 elementData[index] = element; // 元素數量加1 size++; } /** * 檢測指定的index是否越界 */ private void rangeCheckForAdd(int index) { if (index > size || index < 0) { throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } }
2.4 陣列擴容
前面每次add的時候,都會呼叫ensourCapacityInternal(size+1),這個方法裡面就會判斷是否需要進行擴容,如果需要擴容則會進行擴容操作。
除此之外,該方法還有另外一個重要的操作,就是modCount++,記錄陣列的變化次數,用於快速失敗。
/** * 確保內部陣列的容量滿足minCapacity * * @param minCapacity 期望的最小容量 */ private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } /** * 計算容量(正確容量) * * @param elementData 儲存元素的陣列 * @param minCapacity 期望的最小容量 * @return 確定容量 */ private static int calculateCapacity(Object[] elementData, int minCapacity) { // 如果陣列為空陣列,那麼就取minCapacity和預設容量10的較大值 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } // 陣列不為空,直接返回minCapacity return minCapacity; } private void ensureExplicitCapacity(int minCapacity) { // 記錄陣列的修改次數 modCount++; // 是否minCapacity超過當前陣列的長度(當前容量),證明需要擴容(擴容的時機,重要!!!!) if (minCapacity - elementData.length > 0) { grow(minCapacity); } } /** * 陣列擴容 * * @param minCapacity 希望擴容後的陣列長度 */ private void grow(int minCapacity) { int oldCapacity = elementData.length; // 舊陣列的長度 int newCapacity = oldCapacity + (oldCapacity >> 1); // 新陣列的容量(舊陣列的1.5倍) // 判斷計算後的新陣列容量是否滿足要求的最小容量 // 如果不滿足,則直接將新陣列的容量設定為需要的容量 if (newCapacity - minCapacity < 0) { newCapacity = minCapacity; } // 如果申請的容量大於允許申請的最大容量,則返回允許申請的最大容量 if (newCapacity - MAX_ARRAY_SIZE > 0) { newCapacity = hugeCapacity(minCapacity); } // 申請一個新陣列(新容量),然後將原陣列的資料拷貝到新陣列中 elementData = Arrays.copyOf(elementData, newCapacity); }
從原始碼中可以看出,當底層陣列滿的時候(插入新元素,size+1,超過底層陣列的容量),則需要進行擴容,擴容時,會建立1.5倍舊陣列的新陣列,並將舊陣列的元素拷貝到新陣列中(順序不會變)。
2.5 刪除元素
刪除元素有兩種方式,分別是刪除指定元素和刪除指定下標的元素。
刪除元素之後,如果刪除的不是最後一個元素,則需要將刪除元素後面的元素往前挪一個位置,並將空出來的最後一個位置置位null(help GC)。
/** * 刪除指定位置的元素 */ public E remove(int index) { // 判斷陣列是否越界 rangeCheck(index); // 修改次數加1 modCount++; // 從陣列中返回下標為index的元素 E oldValue = elementData(index); // 計算需要移動的元素數量 int numMoved = size - index - 1; // 如果需要移動的元素數量大於0(也就是刪除的元素不是最後一個元素) // 則將index+1開始元素一次往前移動一個位置 if (numMoved > 0) { System.arraycopy(elementData, index + 1, elementData, index, numMoved); } // 將最後一個元素的位置刪除(置位null,讓GC的時候清除對應的記憶體) elementData[--size] = null; // 返回被刪除的元素 return oldValue; } /** * 判斷陣列是否越界 * * @param index 指定陣列下標 */ private void rangeCheck(int index) { if (index >= size) { throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } } /** * 返回陣列中下標為index的元素 * @param index * @return */ @SuppressWarnings("unchecked") E elementData(int index) { return (E) elementData[index]; } /*--------------------------------------------------------*/ /** * 刪除指定元素(遍歷陣列中的元素,刪除第一個匹配的元素) * * @return true:刪除元素; false:未找到要刪除的元素 */ public boolean remove(Object o) { 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++) { // 對於非null的元素,使用equals方法進行比較 if (o.equals(elementData[index])) { fastRemove(index); return true; } } } return false; } /** * 快速刪除指定下標的元素(不檢測陣列越界,不返回被刪除的元素) */ private void fastRemove(int index) { // 修改次數加1 modCount++; // 判斷需要移動的元素數量 int numMoved = size - index - 1; // 如果需要移動的元素數量大於0(刪除的不是最後一個元素) // 則將index後面的元素一次往前移動 if (numMoved > 0) { System.arraycopy(elementData, index + 1, elementData, index, numMoved); } // 將陣列空出的最後一個位置置位null(GC時清除元素),然後陣列元素減1 elementData[--size] = null; // clear to let GC do its work }
2.6 陣列縮容
可以思考這種場景,一個ArrayList,剛開始初始容量為0,新增元素後,容量分別調為10、16...1024,但是後期陣列元素在不斷的刪除元素,雖然陣列中只剩下了5個元素,但是陣列的長度是1024,這是極大地浪費,此時需要進行縮容,就是讓底層陣列調整為適合長度,最適合的長度就是剛好和元素數量個數相同。
ArrayList提供了trimToSize方法,就是進行縮容操作,將底層陣列設定為剛好裝下現有元素的長度
/** * 縮容操作,將底層陣列組調整為size長度 */ public void trimToSize() { // 修改次數加1 modCount++; // 如果陣列中的元素數量小於陣列的長度,才進行縮容 // 如果陣列中沒有元素,則將底層陣列切換為一個空陣列,否則就換為size大小的陣列 if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } }
2.7 獲取元素
獲取元素的過程,比較簡單:
/** * 返回ArrayList中index下標的元素 */ public E get(int index) { // 陣列越界檢測 rangeCheck(index); // 返回index下標的元素 return elementData(index); } /** * 判斷陣列是否越界 * * @param index 指定陣列下標 */ private void rangeCheck(int index) { if (index >= size) { throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } } /** * 返回陣列中下標為index的元素 * * @param index * @return */ @SuppressWarnings("unchecked") E elementData(int index) { return (E) elementData[index]; }