在前面幾篇,咱們已經學習了常見了Map,下面開始閱讀實現Collection接口的常見的實現類。在有了以前源碼的鋪墊以後,咱們後面的閱讀之路將會變得簡單不少,由於不少Collection的結構與Map的相似,甚至有很多是直接用了Map裏的方法。接下來讓咱們一塊兒來看一下ArrayList
的源碼。java
顧名思義,ArrayList的結構實際就是一個Object[]
。因此它的特性很明顯,插入一個元素的時候,是耗時是一個常量時間O(1),在插入n個元素的時候,須要的時間就是O(n)。其餘的操做中,運行的時間也是一個線性的增加(與數組中的元素個數有關)。算法
其中值得一提的是RandomAccess
接口,該接口的目的是這麼說的:數組
List
實現所使用的標記接口,用來代表其支持快速(一般是固定時間)隨機訪問。此接口的主要目的是容許通常的算法更改其行爲,從而在將其應用到隨機或連續訪問列表時能提供良好的性能。dom
對於順序訪問的list,好比LinkedList,使用Iterator訪問會比使用for-i來遍歷list更快。這一點其實很好理解,當對於LinkedList使用get(i)的時候,因爲是鏈表結構,因此每次都會從表頭開始向下搜索,耗時確定會多。
對於實現RandomAccess這個接口的類,如ArrayList,咱們在遍歷的時候,使用for(int i = 0; i < size; i++)
來遍歷,其速度比使用Iterator快(接口上是這麼寫的)。可是筆者看源碼的時候,Iterator裏使用的也是i++
,這種遍歷,無非是增長了fail-fast判斷,估計就是這個致使了性能的差距,可是沒有LinkedList這麼大。筆者循環了 1000 * 1000 次,貼出比較結果,僅供參考,有興趣的朋友們能夠試一試,循環次數越多越明顯:函數
----------now is arraylist---------- 使用Iterator迭代一共花了19ms時間 使用for-i迭代一共花了9ms時間 ----------now is linkedList---------- 使用Iterator迭代一共花了17ms時間 使用for-i迭代一共花了434ms時間
而其繼承的AbstractList主要給ArrayList提供了諸如add
,get
,set
,remove
的集合方法。oop
//初始化默認容量 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; // 數組能申請的最大數量 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
特別說明一下: EMPTY_ELEMENTDATA 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA是爲了在調用構造方法的時候,給elementData數組初始化,當elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA
的時候,當ArrayList第一次插入元素的時候,它的數組大小將會被初始化爲DEFAULT_CAPACITY。而EMPTY_ELEMENTDATA能夠理解爲初始化的時候size=0,下面讓咱們看下構造方法,來更加清楚的理解。性能
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
當調用默認構造函數的時候,給elementData指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA。學習
public ArrayList(int initialCapacity) { // 當 initialCapacity > 0 時,初始化對應大小的數組 if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; // 爲 0 時,用指向EMPTY_ELEMENTDATA } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
這裏當initialCapacity=0的時候,就是上述提到的狀況。this
public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray不返回Object[]的時候,則進行數組拷貝 if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // 若是爲空,則指向EMPTY_ELEMENTDATA this.elementData = EMPTY_ELEMENTDATA; } }
public E get(int index) { rangeCheck(index); return elementData(index); } E elementData(int index) { return (E) elementData[index]; }
get方法很簡單,就是先檢查index範圍是否正確,正確的話從數組裏取出元素。3d
private void rangeCheck(int index) { // 若是index 大於 存儲的個數,則拋出異常 if (index >= size) // outOfBoundsMsg裏面就是簡單的字符串拼接。 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
這裏值得一提的是:這裏只判斷了index >= size
的狀況,對於index < 0
的狀況沒有判斷,是由於在獲取數組值的時候,若是爲負數會拋出ArrayIndexOutOfBoundsException異常。
在看源碼以前,咱們先思考一個問題,往數組裏添加元素的時候要注意什麼:
知道這些須要考慮以後,咱們再來看看它的代碼:
public boolean add(E e) { //對上述的3個前提進行判斷 ensureCapacityInternal(size + 1); //賦值,而後指針走到下一個空位 elementData[size++] = e; return true; }
咱們接着來看ensureCapacityInternal()的方法代碼:
private void ensureCapacityInternal(int minCapacity) { // 上述狀況一:初始化數組的大小 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 取minCapacity和DEFAULT_CAPACITY中較大的那個 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } // 檢查有沒有擴容的必要 ensureExplicitCapacity(minCapacity); }
ensureCapacityInternal()方法的做用就是對構造方法初始化的數組進行處理。
再來看一下ensureExplicitCapacity():
private void ensureExplicitCapacity(int minCapacity) { // 修改次數的計數器(在AbstractList中定義的) modCount++; // 若是須要的空間大小 > 當前數組的長度,則進行擴容 if (minCapacity - elementData.length > 0) grow(minCapacity); }
ensureExplicitCapacity()檢查是否須要擴容。
private void grow(int minCapacity) { // 記錄舊的length 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 = Arrays.copyOf(elementData, newCapacity); } // 最大的容量 private static int hugeCapacity(int minCapacity) { // 大小溢出 if (minCapacity < 0) throw new OutOfMemoryError(); // 須要的最小容量 > 數組最大的長度,則取Integer的最大值,不然取數組最大長度 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
最後的grow()擴容就是判斷有沒有超過數組的最大長度,以及對應的處理。
public E remove(int index) { rangeCheck(index); // 修改計數器 modCount++; // 記錄舊值,返回 E oldValue = elementData(index); // 計算要往前移動的元素個數 int numMoved = size - index - 1; //個數大於0,進行拷貝,從index+1開始,拷貝numMoved個,拷貝起始位置是index if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); // 設置爲null,以便GC elementData[--size] = null; return oldValue; }
對於被刪除的元素,其後面的元素須要往前移。
public boolean remove(Object o) { // 判斷o爲null,loop遍歷找到爲null的元素 if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } // 不爲null } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } // 與上面的remove(int 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; }
public E set(int index, E element) { rangeCheck(index); // 記錄舊的值 E oldValue = elementData(index); //在原位置設置新的值 elementData[index] = element; return oldValue; }
設置index位置的元素值爲element,返回該位置的原來的值
public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; // 對於新的最小長度進行判斷處理 ensureCapacityInternal(size + numNew); //將a數組,從index-0開始,拷貝numNew個,到elementData的size位置 System.arraycopy(a, 0, elementData, size, numNew); //將size增長numNew個 size += numNew; return numNew != 0; }
ArrayList在隨機訪問的時候,數組的結構致使訪問效率比較高,可是在指定位置插入,以及刪除的時候,須要移動大量的元素,致使效率低下,在使用的時候要根據場景特色來選擇,另外注意循環訪問的方式選擇。最後謝謝各位園友觀看,若是有描述不對的地方歡迎指正,與你們共同進步!