1. 程式人生 > >ArrayList集合(JDK1.8) 【集合框架】JDK1.8原始碼分析之ArrayList(六)

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

簡述

  List是繼承於Collection介面,除了Collection通用的方法以外,擴充套件了部分只屬於List的方法。

 

常用子類

 ?ArrayList介紹

1.資料結構

  其底層的資料結構是陣列,陣列元素型別為Object型別,即可以存放所有型別資料。

2.原始碼分析

  2.1類的繼承關係

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

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

  2.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; }

 2.3類的建構函式

 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);
        }
    }

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

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); // 不為Object陣列的話就進行復制 } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }

 說明:當傳入的是集合時,會將其轉成陣列進行處理,並且指定elementData陣列的大小,不允許初始化大小小於0,否則丟擲異常。

2.4核心方法

 1.add函式

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

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 判斷元素陣列是否為空陣列
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 取較大值
        }       
        ensureExplicitCapacity(minCapacity);
    }
private void ensureExplicitCapacity(int minCapacity) { // 結構性修改加1 modCount++; if (minCapacity - elementData.length > 0) grow(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); }
 private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

說明:在這裡可以很清楚的看到擴容容量的計算:int newCapacity = oldCapacity + (oldCapacity >> 1),其中oldCapacity是原來的容量大小,oldCapacity >> 1 為位運算的右移操作,右移一位相當於除以2,所以這句程式碼就等於int newCapacity = oldCapacity + oldCapacity / 2;即容量擴大為原來的1.5倍。,獲取newCapacity後再對newCapacity的大小進行判斷,如果仍然小於minCapacity,則直接讓newCapacity 等於minCapacity,而不再計算1.5倍的擴容。然後還要再進行一步判斷,即判斷當前新容量是否超過最大的容量 if (newCapacity - MAX_ARRAY_SIZE > 0),如果超過,則呼叫hugeCapacity方法,傳進去的是minCapacity,即新增元素後需要的最小容量,如果minCapacity大於MAX_ARRAY_SIZE,則返回Integer的最大值。否則返回MAX_ARRAY_SIZE。
注意:陣列在進行擴容時,會將老陣列中的元素重新拷貝一份到新的陣列中,每次陣列容量的增長大約是其原容量的1.5倍,操作代價很高,最好在預先元素個數有多少個的情況下指定其容量。
舉個栗子

說明:可以看出定義一個無參建構函式時,容器為一個空的陣列,並且此時的elementData的值為0

 

說明:在呼叫了add函式之後,elementData的值被設定成初始值10,並且將元素1新增進陣列中,並且size值加1

 

 舉個栗子

 

說明:當如果一開始定義好了集合的長度,那麼在一開始初始化容器的時候,elementData的值就被設定好了

 

說明:而如果在後邊新增的元素大於原先初始化的大小,那麼ArrayList的大小會按照1.5倍的來進行動態擴建。如上,原先elementData的值為6,被擴大1.5後變成9

 

說明:無參建構函式也是相同的道理,如果新增的元素長度大於預設值10,會進行1.5倍進行動態擴建,即elementData的值變成15

 附:size和modCount的區別

 size是ArrayList的變數。modCount是ArrayList的父類AbstractList中的變數,預設值為0。

  size記錄了ArrayList中元素的數量,modCount記錄的是關於元素的數目被修改的次數。modCount在ArrayList的普通操作裡可能並沒有看出多大用處,但是在涉及到fail-fast就主要是依靠它了。

 舉個栗子

 

說明:一開始進行add操作之後,size和modCount的值都是相同的,每次執行都進行加1

 

說明:當進行remove操作後,容器的元素個數發生改變,所以modCount值會進行加1,而size由於元素減少,進行減1

 

說明:當再次進行add操作後,size和modCount值同時也會加1

 

說明:進行set操作進行修改容器元素後,由於元素個數並沒有發生改變,所以modCount和size的值都不會發生變化

 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);
    }

 private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

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

說明:get函式會檢查索引值是否合法(只檢查是否大於size,而沒有檢查是否小於0)。在get函式中存在element函式,element函式用於返回具體的元素,返回的值都經過了向下轉型(Object -> E)

舉個栗子

說明:當index為負值時,程式執行會發生陣列越界異常java.lang.ArrayIndexOutOfBoundsException

 

說明:而當index大於容器中的size值時,則會發生索引越界異常java.lang.IndexOutOfBoundsException

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,可以作為小的技巧使用。

 

註明:本人才疏學淺,以上僅為個人日常筆記總結使用

參考資料:【集合框架】JDK1.8原始碼分析之ArrayList(六)

                  淺談ArrayList動態擴容

                  <<Thinking In Java(中文版第四版>>