1. 程式人生 > >JAVA集合原始碼學習——ArrayList

JAVA集合原始碼學習——ArrayList

JAVA集合原始碼學習——ArrayList

ArrayList概述

1)ArrayList是基於長度可動態增長的陣列實現List介面的java集合類
2)ArrayList類內部維護者一個動態可再分配的Object[]陣列,每一個類物件都有一個capacity屬性,表示所封裝的Object[]物件陣列的長度,但往ArrayList新增元素時,該屬性值會自動增加,體現了動態的特點
3)如果要往ArrayList裡一次新增大量元素可以呼叫ensureCapacity方法傳入滿足新增元素的最終容量,減少ArrayList每次自動重分配的次數從而提高效能
4)ArrayList和vector區別是:ArrayList是非同步的即執行緒不安全,當有多執行緒訪問該集合時,程式要手動保證集合的同步;相反vector則是同步,執行緒安全的
5)ArrayList和Collection關係:
ArrayList繼承關係圖

ArrayList資料結構

arrayList的資料結構:
arryList資料結構圖arryList底層的資料結構就是陣列,所有對ArrayList的操作都是基於對陣列的操作

ArrayList原始碼分析

1、繼承結構圖
ArrayList繼承圖其繼承結構:
ArrayList extends AbstractList
AbstractList extends AbstractCollection
分析:
1)ArrayList為什麼要繼承abstractList而不直接實現list介面?其實採用的就是介面卡的思想,方便ArrayList的程式碼更簡潔
2)ArrayList實現的介面
ArrayList實現介面
RandomAccess介面:標記性介面,用於快速存取,即實現了該介面後用for迴圈遍歷比使用迭代器迭代的效率更高
3)Cloneable介面:可以使用Object.Clone()方法
4)Serializable介面:實現序列化介面,可以實現序列化功能儲存
5)類中屬性
ArrayList內部引數

6)建構函式
arryList建構函式
1、無參建構函式

Constructs an empty list with an initial capacity of ten.  這裡就說明了預設會給10的大小,所以說一開始arrayList的容量是10.ArrayList中儲存資料的其實就是一個數組,這個陣列就是elementData
private transient Object[] elementData;
public ArrayList() {  
    super();        //呼叫父類中的無參構造方法,父類中的是個空的構造方法
    this.elementData = EMPTY_ELEMENTDATA;//EMPTY_ELEMENTDATA:
    是個空的Object[], 將elementData初始化,elementData也是個Object[]型別。
    空的Object[]會給預設大小10,等會會解釋什麼時候賦值的。
}

2、有參建構函式

 * Constructs an empty list with the specified initial capacity.
 *
 * @param  initialCapacity  the initial capacity of the list
 * @throws IllegalArgumentException if the specified initial capacity
 *         is negative
 */
public ArrayList(int initialCapacity) {
    super(); //父類中空的構造方法
    if (initialCapacity < 0)    //判斷如果自定義大小的容量小於0,則報下面這個非法資料異常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity]; //將自定義的容量大小當成初始化elementData的大小
}

7)核心方法
1、boolean add(E);//預設直接在末尾加元素

 *Appends the specified element to the end of this list.新增一個特定的元素到list的末尾。
 *@param e element to be appended to this list
 *@return <tt>true</tt> (as specified by {@link Collection#add}
 */
public boolean add(E e) {    
//確定內部容量是否夠了,size是陣列中資料的個數,因為要新增一個元素,所以size+1,先判斷size+1的這個個數陣列能否放得下,就在這個方法中去判斷是否陣列.length是否夠用了。
    ensureCapacityInternal(size + 1);  // Increments modCount!!
 //在資料中正確的位置上放上元素e,並且size++
    elementData[size++] = e;
    return true;
}

分析:
ensureCapacityInternal(xxx);確定內部容量的方法

*
*@param
*/
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == EMPTY_ELEMENTDATA) {//看,判斷初始化的elementData是不是空的陣列,也就是沒有長度
//因為如果是空的話,minCapacity=size+1;其實就是等於1,空的陣列沒有長度就存放不了,所以就將minCapacity變成10,也就是預設大小,但是帶這裡,還沒有真正的初始化這個elementData的大小。
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
//確認實際的容量,上面只是將minCapacity=10,這個方法就是真正的判斷elementData是否夠用
    ensureExplicitCapacity(minCapacity);
}

ensureExplicitCapacity(xxx);

 *
 *@param
*/
 private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious codeminCapacity如果大於了實際elementData的長度,那麼就說
    elementData陣列的長度不夠用,不夠用那麼就要增加elementData的length。
    minCapacity到底是什麼呢,這裡給你們分析一下
    第一種情況:由於elementData初始化時是空的陣列,那麼第一次add的時候,minCapacity=size+1;也就minCapacity=1,在上一個方法(確定內部容量ensureCapacityInternal)就會判斷出是空的陣列,就會給將minCapacity=10,到這一步為止,還沒有改變elementData的大小
    第二種情況:elementData不是空的陣列了,那麼在add的時候,minCapacity=size+1;也就是minCapacity代表著elementData中增加之後的實際資料個數,拿著它判斷elementData的length是否夠用,如果lengt不夠用,那麼肯定要擴大容量,不然增加的這個元素就會溢位。 if (minCapacity - elementData.length > 0)
//arrayList能自動擴充套件大小的關鍵方法就在這裡了
        grow(minCapacity);
}

grow(minCapacity)

*
 *@param
 */
 private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;  //將擴充前的elementData大小給oldCapacity
    int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity就是1.5倍的oldCapacity
    if (newCapacity - minCapacity < 0)//這句話就是適應於elementData就空陣列的時候,length=0,那麼oldCapacity=0,newCapacity=0,所以這個判斷成立,在這裡就是真正的初始化elementData的大小了,就是為10.前面的工作都是準備工作。
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)//如果newCapacity超過了最大的容量限制,就呼叫hugeCapacity,也就是將能給的最大值給newCapacity
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
//新的容量大小已經確定好了,就copy陣列,改變容量大小咯。
    elementData = Arrays.copyOf(elementData, newCapacity);
}

hugeCapacity()

 *@param
 */
 //這個就是上面用到的方法,很簡單,就是用來賦最大值。
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();//如果minCapacity都大於MAX_ARRAY_SIZE,那麼Integer.MAX_VALUE返回,反之將MAX_ARRAY_SIZE返回。因為maxCapacity是三倍的minCapacity,可能擴充的太大了,就用minCapacity來判斷了。//Integer.MAX_VALUE:2147483647MAX_ARRAY_SIZE:2147483639  
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

2、void add(int , E);//在特定位置新增元素

 *
 */
 public void add(int index, E element) {
    rangeCheckForAdd(index);//檢查index也就是插入的位置是否合理。//跟上面的分析一樣,具體看上面
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //這個方法就是用來在插入元素之後,要將index之後的元素都往後移一位,
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
                     //在目標位置上存放元素
    elementData[index] = element;
    size++;//size增加1
}

rangeCheckForAdd(index)
rangeCheckForAddSystem.arraycopy(xxx)就是把elementData在插入位置後的所有元素往後面移一位

小結:
正常情況下會擴容1.5倍,特殊情況下(新擴充套件陣列大小已經達到了最大值)則只取最大值
當我們呼叫add方法時,實際的函式呼叫
add函式底層呼叫3)remove(int);//刪除指定位置上的元素

 *@
 */
 public E remove(int index) {
    rangeCheck(index);//檢查index的合理性

    modCount++;//這個作用很多,比如用來檢測快速失敗的一種標誌。
    E oldValue = elementData(index);//通過索引直接找到該元素

    int numMoved = size - index - 1;//計算要移動的位數。
    if (numMoved > 0)
    //這個方法也已經解釋過了,就是用來移動元素的。
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
                         //將--size上的位置賦值為null,讓gc(垃圾回收機制)更快的回收它。
    elementData[--size] = null; // clear to let GC do its work
    //返回刪除的元素。
    return oldValue;
}

4)remove(Object);//從這裡可以看ArrayList是可以存放null值

 *
 */
 //感覺這個不怎麼要分析吧,都看得懂,就是通過元素來刪除該元素,就依次遍歷,如果有這個元素,就將該元素的索引傳給fastRemobe(index),使用這個方法來刪除該元素,
 //fastRemove(index)方法的內部跟remove(index)的實現幾乎一樣,這裡最主要是知道arrayList可以儲存null值
   public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

5)clear();//將elementData中每個元素賦值為null,等待垃圾回收回收

 *@return <tt>true</tt> (as specified by {@link Collection#add}
 */
 public void clear() {
    modCount++;

    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
}

6)set()方法
set方法原始碼圖
7)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;
}

8)get(index)方法
get方法圖

總結

1)ArrayList可以存放null
2)ArrayList本質是一個elementData陣列
3)ArrayList區別於一般陣列是在於自動擴充套件大小,依賴的方法就是grow()方法
4)ArrayList由於資料結構是陣列因此查詢資料很快但刪除和插入方面效能不高
5)ArrayList實現了RandomAccess,所以遍歷時推薦使用for迴圈