【集合框架】JDK1.8原始碼分析之ArrayList(六)

leesf發表於2016-03-22

一、前言

  分析了Map中主要的類之後,下面我們來分析Collection下面幾種常見的類,如ArrayList、LinkedList、HashSet、TreeSet等。下面通過JDK原始碼來一起分析ArrayList底層是如何實現的。(PS:把JVM看完了之後終於可以有成片的時間來閱讀原始碼了,感覺簡直不能更爽)。

二、ArrayList資料結構

  分析一個類的時候,資料結構往往是它的靈魂所在,理解底層的資料結構其實就理解了該類的實現思路,具體的實現細節再具體分析。

  ArrayList的資料結構如下:

  

  說明:底層的資料結構就是陣列,陣列元素型別為Object型別,即可以存放所有型別資料。我們對ArrayList類的例項的所有的操作底層都是基於陣列的。下面我們來分析通過陣列是如何保證庫函式的正確實現的。

三、ArrayList原始碼分析

  3.1 類的繼承關係

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable

  說明:ArrayList繼承AbstractList抽象父類,實現了List介面(規定了List的操作規範)、RandomAccess(可隨機訪問)、Cloneable(可拷貝)、Serializable(可序列化)。

  3.2 類的屬性  

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;
}
View Code

  說明:類的屬性中核心的屬性為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);
        }
    }
View Code

  說明:指定elementData陣列的大小,不允許初始化大小小於0,否則丟擲異常。

  2. ArrayList()型建構函式

    public ArrayList() { 
        // 無參建構函式,設定元素陣列為空 
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
View Code

  說明:當未指定初始化大小時,會給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;
        }
    }
View Code

  說明:當傳遞的引數為集合型別時,會把集合型別轉化為陣列型別,並賦值給elementData。

  3.4 核心函式分析

  1. add函式

    public boolean add(E e) { // 新增元素
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
View Code

  說明:在add函式我們發現還有其他的函式ensureCapacityInternal,此函式可以理解為確保elementData陣列有合適的大小。ensureCapacityInternal的具體函式如下 

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 判斷元素陣列是否為空陣列
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 取較大值
        }
        
        ensureExplicitCapacity(minCapacity);
    }
View Code

  說明:在ensureCapacityInternal函式中我們又發現了ensureExplicitCapacity函式,這個函式也是為了確保elemenData陣列有合適的大小。ensureExplicitCapacity的具體函式如下

private void ensureExplicitCapacity(int minCapacity) {
        // 結構性修改加1
        modCount++;
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
View Code

  說明:在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);
    }
View Code

  說明:正常情況下會擴容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;
    }
View Code

  說明:設定指定下標索引的元素值。  

  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;
    }
View Code

  說明:從頭開始查詢與指定元素相等的元素,注意,是可以查詢null元素的,意味著ArrayList中可以存放null元素的。與此函式對應的lastIndexOf,表示從尾部開始查詢。

  4. get函式

    public E get(int index) {
        // 檢驗索引是否合法
        rangeCheck(index);

        return elementData(index);
    }
View Code

  說明:get函式會檢查索引值是否合法(只檢查是否大於size,而沒有檢查是否小於0),值得注意的是,在get函式中存在element函式,element函式用於返回具體的元素,具體函式如下 

    E elementData(int index) {
        return (E) elementData[index];
    }
View Code

  說明:返回的值都經過了向下轉型(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;
    }
View Code

  說明:remove函式使用者移除指定下標的元素,此時會把指定下標到陣列末尾的元素向前移動一個單位,並且會把陣列最後一個元素設定為null,這樣是為了方便之後將整個陣列不被使用時,會被GC,可以作為小的技巧使用。

四、總結

  ArrayList有其特殊的應用場景,與LinkedList相對應。其優點是隨機讀取,缺點是插入元素時需要移動大量元素,效率不太高。至此,ArrayList的原始碼分析就到這裡,總體來說,ArrayList的底層還是很簡單的,謝謝各位園友的觀看~

 

  

相關文章