1、前言java
分析了Map中主要的類以後,下面咱們來分析Collection下面幾種常見的類,如ArrayList、LinkedList、HashSet、TreeSet等。下面經過JDK源碼來一塊兒分析ArrayList底層是如何實現的。(PS:把JVM看完了以後終於能夠有成片的時間來閱讀源碼了,感受簡直不能更爽)。數組
2、ArrayList數據結構數據結構
分析一個類的時候,數據結構每每是它的靈魂所在,理解底層的數據結構其實就理解了該類的實現思路,具體的實現細節再具體分析。dom
ArrayList的數據結構以下:ide
說明:底層的數據結構就是數組,數組元素類型爲Object類型,便可以存放全部類型數據。咱們對ArrayList類的實例的全部的操做底層都是基於數組的。下面咱們來分析經過數組是如何保證庫函數的正確實現的。函數
3、ArrayList源碼分析源碼分析
3.1 類的繼承關係this
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
說明:ArrayList繼承AbstractList抽象父類,實現了List接口(規定了List的操做規範)、RandomAccess(可隨機訪問)、Cloneable(可拷貝)、Serializable(可序列化)。spa
3.2 類的屬性 code
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 = {}; // 缺省空對象數組 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 元素數組 transient Object[] elementData; // 實際元素大小,默認爲0 private int size; // 最大數組容量 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; }
說明:類的屬性中核心的屬性爲elementData,類型爲Object[],用於存放實際元素,而且被標記爲transient,也就意味着在序列化的時候,此字段是不會被序列化的。
3.3 類的構造函數
1. ArrayList(int)型構造函數
public ArrayList(int initialCapacity) { if (initialCapacity > 0) { // 初始容量大於0 this.elementData = new Object[initialCapacity]; // 初始化元素數組 } else if (initialCapacity == 0) { // 初始容量爲0 this.elementData = EMPTY_ELEMENTDATA; // 爲空對象數組 } else { // 初始容量小於0,拋出異常 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
說明:指定elementData數組的大小,不容許初始化大小小於0,不然拋出異常。
2. ArrayList()型構造函數
public ArrayList() { // 無參構造函數,設置元素數組爲空 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
說明:當未指定初始化大小時,會給elementData賦值爲空集合。
3. ArrayList(Collection<? extends E>)型構造函數
public ArrayList(Collection<? extends E> c) { // 集合參數構造函數 elementData = c.toArray(); // 轉化爲數組 if ((size = elementData.length) != 0) { // 參數爲非空集合 if (elementData.getClass() != Object[].class) // 是否成功轉化爲Object類型數組 elementData = Arrays.copyOf(elementData, size, Object[].class); // 不爲Object數組的話就進行復制 } else { // 集合大小爲空,則設置元素數組爲空 this.elementData = EMPTY_ELEMENTDATA; } }
說明:當傳遞的參數爲集合類型時,會把集合類型轉化爲數組類型,並賦值給elementData。
3.4 核心函數分析
1. add函數
public boolean add(E e) { // 添加元素 ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
說明:在add函數咱們發現還有其餘的函數ensureCapacityInternal,此函數能夠理解爲確保elementData數組有合適的大小。ensureCapacityInternal的具體函數以下
private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 判斷元素數組是否爲空數組 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 取較大值 } ensureExplicitCapacity(minCapacity); }
說明:在ensureCapacityInternal函數中咱們又發現了ensureExplicitCapacity函數,這個函數也是爲了確保elemenData數組有合適的大小。ensureExplicitCapacity的具體函數以下
private void ensureExplicitCapacity(int minCapacity) { // 結構性修改加1 modCount++; if (minCapacity - elementData.length > 0) grow(minCapacity); }
說明:在ensureExplicitCapacity函數咱們又發現了grow函數,grow函數纔會對數組進行擴容,ensureCapacityInternal、ensureExplicitCapacity都只是過程,最後完成實際擴容操做仍是得看grow函數,grow函數的具體函數以下
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); }
說明:正常狀況下會擴容1.5倍,特殊狀況下(新擴展數組大小已經達到了最大值)則只取最大值。
當咱們調用add方法時,實際上的函數調用以下
說明:程序調用add,實際上還會進行一系列調用,可能會調用到grow,grow可能會調用hugeCapacity。
下面經過兩種方式給出調用add的例子,並分析最後的elementData數組的大小。
示例一(只給出了會影響到最終結果的核心代碼)
List<Integer> lists = new ArrayList<Integer>(); lists.add(8);
說明:初始化lists大小爲0,調用的ArrayList()型構造函數,那麼在調用lists.add(8)方法時,會通過怎樣的步驟呢?下圖給出了該程序執行過程和最初與最後的elementData的大小
說明:咱們能夠看到,在add方法以前開始elementData = {};調用add方法時會繼續調用,直至grow,最後elementData的大小變爲10,以後再返回到add函數,把8放在elementData[0]中。
示例二核心代碼以下
List<Integer> lists = new ArrayList<Integer>(6); lists.add(8);
說明:調用的ArrayList(int)型構造函數,那麼elementData被初始化爲大小爲6的Object數組,在調用add(8)方法時,具體的步驟以下:
說明:咱們能夠知道,在調用add方法以前,elementData的大小已經爲6,以後再進行傳遞,不會進行擴容處理。
2. set函數
public E set(int index, E element) { // 檢驗索引是否合法 rangeCheck(index); // 舊值 E oldValue = elementData(index); // 賦新值 elementData[index] = element; // 返回舊值 return oldValue; }
說明:設定指定下標索引的元素值。
3. indexOf函數
// 從首開始查找數組裏面是否存在指定元素 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; }
說明:從頭開始查找與指定元素相等的元素,注意,是能夠查找null元素的,意味着ArrayList中能夠存放null元素的。與此函數對應的lastIndexOf,表示從尾部開始查找。
4. get函數
public E get(int index) { // 檢驗索引是否合法 rangeCheck(index); return elementData(index); }
說明:get函數會檢查索引值是否合法(只檢查是否大於size,而沒有檢查是否小於0),值得注意的是,在get函數中存在element函數,element函數用於返回具體的元素,具體函數以下
E elementData(int index) { return (E) elementData[index]; }
說明:返回的值都通過了向下轉型(Object -> E),這些是對咱們應用程序屏蔽的小細節。
5. remove函數
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); // 賦值爲空,有利於進行GC elementData[--size] = null; // 返回舊值 return oldValue; }
說明:remove函數用戶移除指定下標的元素,此時會把指定下標到數組末尾的元素向前移動一個單位,而且會把數組最後一個元素設置爲null,這樣是爲了方便以後將整個數組不被使用時,會被GC,能夠做爲小的技巧使用。
4、總結
ArrayList有其特殊的應用場景,與LinkedList相對應。其優勢是隨機讀取,缺點是插入元素時須要移動大量元素,效率不過高。至此,ArrayList的源碼分析就到這裏,整體來講,ArrayList的底層仍是很簡單的,謝謝各位園友的觀看~