JDK1.8之ArrayList


轉載總結

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;
    // 最大數組容量

    /*
    數組所能開闢的最大長度
    因爲有些虛擬機保留了一些header words在數組中
    嘗試要開闢更大的長度的數組,可能會出現OOM異常(在一些虛擬機實現中)
     */
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賦值爲缺省空集合。

 /*
    無參的構造函數,在該構造函數中,會將上述的靜態的DEFAULTCAPACITY_EMPTY_ELEMENTDATA屬性,賦值給elementData屬性
    也即我們用這種方法構造ArraList的時候,並不會真正產生實例化的數組,而是引用一個靜態的空數組
     */

 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的具體函數如下

 首先會對modCount+1,modCount是AbstractList類中的一個成員變量,該值表示對List的修改次數,主要是爲了服務快速失敗功能的

複製代碼
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。

 /*
    插入一個元素element到指定index位置,原位置的元素依次向後移動一位
    改方法效率要低一些,如果並不是特定必須要塞入哪個位置的話,最好不要用
     */
    public void add(int index, E element) {
        //首先會去檢查一下index是否可以使用
        rangeCheckForAdd(index);
        //確保數組可容納
        ensureCapacityInternal(size + 1);  // Increments modCount!! 會修改modCount的值,modCount+1
        //隨後調用System.arraycopy方法,將elementData的index位置元素依次向後移動,爲接下來的插入預留空間
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;//真正的插入操作
        size++;//size+1
    }


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;
    }
複製代碼

  

如果index>size的話,會出現數組越界

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

四、總結

  ArrayList有其特殊的應用場景,與LinkedList相對應。其優點是隨機讀取,缺點是插入元素時需要移動大量元素,效率不太高。









發佈了27 篇原創文章 · 獲贊 48 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章