ArrayList源碼學習筆記

1.簡介:


public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  1. 繼承了AbstractList,提供相關的修改、刪除、遍歷等功能
  2. 實現了RandomAccess接口,提供了隨機訪問訪問功能,即可以通過元素序號訪問元素
  3. 實習Cloneable接口,可以克隆
  4. 實現了Serializable接口,實現了序列化的功能

   其他:ArrayList不是線程安全的,多線程可以考慮使用Vector或者CopyOnWriteArrayList

   特點:
     

       1.  不是線程安全的,不是線程同步的。

         2.允許null在內的所有元素。

         3.ArrayList中存放順序和添加順序是一致的。並且可重複元素。

         4. ArrayList實現了可變大小的數組。


 

2.ArrayList屬性:

  

  private static final long serialVersionUID = 8683452581122892189L;

    //數組的初始容量

    private static final int DEFAULT_CAPACITY = 10;
    //當用戶指定容量爲0時
    //elementData=EMPTY_ELEMENTDATA

    private static final Object[] EMPTY_ELEMENTDATA = {};

    //當用戶未指定容量時
    //elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA
    //當用戶第一次添加元素時,會擴容爲10
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    //transient修飾代表不會被序列化
    //ArrayList用該數組保存數據

    transient Object[] elementData; // non-private to simplify nested class access


    private int size;

 3.構造函數

       (1) 由用戶指定容量的構造函數

  public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

      當 initialCapacity=0時,elementData = EMPTY_ELEMENTDATA;

   (2)無參構造函數

     

 public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

 3.重要方法:

     1.add()

      

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

    線程安全:我們注意到elementData[size++] = e;這條語句其實就是添加元素的關鍵,其實這條語句可以拆成兩條語句來執行也就是elementData[size] = e 和 size++,試想一下,如果在多線程訪問同一個list的時候,線程A執行到elementData[size] = e時,時間片到了讓出CPU,此時線程B執行,當線程B執行到elementData[size] = e此時size還沒有加一,(假設此時size=10)也就是說線程A在list[10]位置上添加的元素被線程B添加的元素覆蓋了,然後A,B兩個線程都執行size++;這樣造成的影響就是size變成12了,但是在第11個位置上卻沒有元素也就是說list[11]=null
 

      add方法首先調用ensureCapacityInternal

 private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    可以看出,如果ArrayList是通過無參構造方法初始且第一次add,此時minCapacity=1,然後minCapacity變爲10,然後調用

    ensureExplicitCapacity(10)

    

 private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

   如果minCapacity>當前數組的長度才進行擴容

  

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

           Arrays.copyof調用的是本地方法 System.arraycopy() ;
           Arrays.copyOf()方法返回的數組是新的數組對象,原數組對象仍是原數組對象,不變,該拷貝不會影響原來的數組

           System.arraycopy() 源碼如下:

public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

參數說明:
src:源對象
srcPos:源數組中的起始位置
dest:目標數組對象
destPos:目標數據中的起始位置
length:要拷貝的數組元素的數量

 

        grow分析:先把新容量變爲舊容量1.5倍,如果新容量還是小於指定容量,則新容量=指定容量,如果新容量大於最大容量,則看minCapacity與MAX_ARRAY_SIZE的大小,如果minCapacity>MAX_ARRAY_SIZE則新容量爲Integer.MAX_VALUE否則新容量爲MAX_ARRAY_SIZE

    注意:如果無參構造初始的ArrayList,則在第一次添加元素時就進行擴容,第一次擴容至10,直到容量大於10才進行1.5倍擴容,如果不是無參構造初始的ArrayList,

      總結:add方法: add-> ensureCapacityInternal(是否時無參構造初始的ArrayList)->ensureExplicitCapacity(所需容量是否大於當前容量)->grow(擴容)

  2.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);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

        remove 中主要是將之後的元素都向前一位移動,然後將最後一位的值設置爲空。最後,返回已經刪除的值。

 3.indexof方法

.indexOf方法--查找下標
/**
 * 查找下標, 如果爲null,直接和null比較,返回下標
 */
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;
}
/**
 * 查找最後出現的下標,從大往下循環查找
 */
public int lastIndexOf(Object o) {
    if (o == null) {
        for (int i = size-1; i >= 0; i--)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = size-1; i >= 0; i--)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

4.總結:

    與LinkedList區別
           1. ArrayList的實現是基於數組,LinkedList的實現是基於雙向鏈表。 
          2. 對於隨機訪問,ArrayList優於LinkedList,ArrayList可以根據下標以O(1)時間複雜度對元素進行隨機訪問。而LinkedList的每一個元素都依靠地址指針和它後一個元素連接在一起,在這種情況下,查找某個元素的時間複雜度是O(n)
          3. 對於插入和刪除操作,LinkedList優於ArrayList,因爲當元素被添加到LinkedList任意位置的時候,不需要像ArrayList那樣重新計算大小或者是更新索引。  
      4. LinkedList比ArrayList更佔內存,因爲LinkedList的節點除了存儲數據,還存儲了兩個引用,一個指向前一個元素,一個指向後一個元素。

     å¨è¿éæå¥å¾çæè¿°

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章