jdk原始碼分析之ArrayList

王世暉發表於2016-05-24

ArrayList關鍵屬性分析

ArrayList採用Object陣列來儲存資料

    /**

     * The array buffer into which the elements of the ArrayList are stored.

     * The capacity of the ArrayList is the length of this array buffer. 

     */

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

    /**

     * The size of the ArrayList (the number of elements it contains).

     * @serial

     */

    private int size;

Object[] elementData是一個buffer陣列,用來儲存ArrayList的資料,該陣列的大小表示ArrayList的容量,而size屬性表示的是ArrayList裡邊儲存元素的個數。

    /**

     * Shared empty array instance used for default sized empty instances. We

     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when

     * first element is added.

     */

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

多個ArrayList例項共享的static屬性,一個空陣列的例項,使用ArrayList的無參建構函式建立ArrayList例項的時候,直接使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA給底層陣列elementData賦值

    /**

     * Constructs an empty list with an initial capacity of ten.

     */

    public ArrayList() {

        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

    }

而當建立一個容量為0的ArrayList時,直接將層陣列elementData賦值為另外一個static屬性

    /**

     * Shared empty array instance used for empty instances.

     */

    private static final Object[] EMPTY_ELEMENTDATA = {};

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

        }

    }

從ArrayList獲取資料get(int index)

ArrayList可以通過下標對資料進行隨機訪問,時間複雜度O(1),實現方法為get方法

    public E get(int index) {

        rangeCheck(index);

        return elementData(index);

    }

get方法很簡單,輸入引數合法的話直接返回底層陣列elementData對應位置的元素即可。 
而rangeCheck主要是檢查下標不能越界訪問

    private void rangeCheck(int index) {

        if (index >= size)

            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

}

向ArrayList尾部新增一個資料add(E e)

新增資料有兩個方法,呼叫add(E e) 向ArrayList末尾新增資料和呼叫add(int index, E element)新增資料到指定位置

    public boolean add(E e) {

        ensureCapacityInternal(size + 1);  // Increments modCount!!

        elementData[size++] = e;

        return true;

}

新增資料首先檢查是不是需要擴充容量來新增新的資料,呼叫ensureCapacityInternal(size + 1)確保當前容量足夠新增一個新的資料,然後elementData[size++] = e將新資料新增在陣列的末尾

    private void ensureCapacityInternal(int minCapacity) {

        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);

        }

        ensureExplicitCapacity(minCapacity);

 }

ensureCapacityInternal確保在ArrayList容量為0的時候新增資料擴容時,至少擴容DEFAULT_CAPACITY大小,而DEFAULT_CAPACITY大小預設為10

    /**

     * Default initial capacity.

     */

    private static final int DEFAULT_CAPACITY = 10;

然後呼叫 ensureExplicitCapacity(minCapacity)進行擴容

    private void ensureExplicitCapacity(int minCapacity) {

        modCount++;

        // overflow-conscious code

        if (minCapacity - elementData.length > 0)

            grow(minCapacity);

    }

modCount++用於記錄修改次數,主要用於一個執行緒使用迭代器迭代資料的時候另一個資料更改ArrayList結構丟擲ConcurrentModificationException異常 
if (minCapacity - elementData.length > 0)用於判斷是否真的需要擴容,elementData.length表示的是容器容量,size表示容器儲存資料的數量,而minCapacity =sie+1,因此elementData如果有多於一個位置空閒沒有儲存資料,就不需要擴容 
否則呼叫grow(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);

    }

可以看到擴容時直接將容量大小變為之前的1.5倍

int newCapacity = oldCapacity + (oldCapacity >> 1);

這裡還需要對newCapacity進行調整,如果擴容後的newCapacity還是比minCapacity小 
滿足

if (newCapacity - minCapacity < 0)

設定newCapacity為傳進來的引數minCapacity

newCapacity = minCapacity;

然後判斷現在的newCapacity是否比上限MAX_ARRAY_SIZE大

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

如果newCapacity比MAX_ARRAY_SIZE還大,則需要呼叫hugeCapacity(minCapacity)判斷是否是minCapacity傳入負數溢位

    private static int hugeCapacity(int minCapacity) {

        if (minCapacity < 0// overflow

            throw new OutOfMemoryError();

        return (minCapacity > MAX_ARRAY_SIZE) ?

            Integer.MAX_VALUE :

            MAX_ARRAY_SIZE;

    }

hugeCapacity(int minCapacity)首先檢測是否溢位,沒溢位的話就返回Integer.MAX_VALUE 作為最大值(minCapacity > MAX_ARRAY_SIZE條件在呼叫出就滿足)

經過上述一系列步驟,最終滿足各種條件的新容量值minCapacity得到滿足

   // minCapacity is usually close to size, so this is a win:

    elementData = Arrays.copyOf(elementData, newCapacity);

這裡是直接建立一個容量為newCapacity的新陣列,把之前elementData儲存的資料複製到新陣列。陣列的複製是比較耗費效能的,因此應該避免連續擴容,儘量呼叫有參建構函式ArrayList(int initialCapacity)並設定合理容量大小

向ArrayList任意位置新增一個資料add(int index, E element)

呼叫add(int index, E element)在ArrayList任意位置新增資料

    public void add(int index, E element) {

        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!

        System.arraycopy(elementData, index, elementData, index + 1,

                         size - index);

        elementData[index] = element;

        size++;

    }

傳入引數合法性檢查仍然是方法體第一件要做的事情,這裡要檢測插入資料的位置index是否合法

    private void rangeCheckForAdd(int index) {

        if (index > size || index < 0)

            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

 }

引數合法性通過後呼叫ensureCapacityInternal(size + 1)進行擴容,上邊已經分析過擴容的固定套路。擴容後就是呼叫System.arraycopy進行陣列的複製,由此可見ArrayList新增資料是比較耗費效能的,事件複雜度是O(n),因為插入資料總是伴隨著陣列的複製(陣列元素的移動)。

在ArrayList尾部新增一個Collection集合addAll

    public boolean addAll(Collection<? extends E> c) {

        Object[] a = c.toArray();

        int numNew = a.length;

        ensureCapacityInternal(size + numNew);  // Increments modCount

        System.arraycopy(a, 0, elementData, size, numNew);

        size += numNew;

        return numNew != 0;

    }

先通過Collection的toArray方法把Collection轉化為Object陣列,然後通過ensureCapacityInternal(size + numNew)進行擴容,然後呼叫

System.arraycopy(a, 0, elementData, size, numNew); 
public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length);

System.arraycopy進行陣列的拼接,System.arraycopy(a, 0, elementData, size, numNew)表示從陣列a的下標0出開始複製length個元素導陣列elementData的下標size處。

在ArrayList任意位置新增一個集合Collection

 public boolean addAll(int index, Collection<? extends E> c) {

        rangeCheckForAdd(index);

        Object[] a = c.toArray();

        int numNew = a.length;

        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;

        if (numMoved > 0)

            System.arraycopy(elementData, index, elementData, index + numNew,

                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);

        size += numNew;

        return numNew != 0;

}

仍然是先做入口引數的合法性檢查

    private void rangeCheckForAdd(int index) {

        if (index > size || index < 0)

            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    }

然後把集合轉化為Object陣列

Object[] a = c.toArray();

通過ensureCapacityInternal(size + numNew)進行擴容 
計算原陣列需要移動的資料的個數int numMoved = size - index 
如果有資料需要移動,呼叫System.arraycopy進行資料整體移動

System.arraycopy(elementData, index, elementData, index + numNew, numMoved);

從elementData陣列下標為index出複製numMoved個資料到elementData陣列的下標為index + numNew處 
這樣elementData陣列下標為index到index+numNew-1的位置被空出來,用於放置新的資料 
然後集合的資料新增到elementData陣列留出的位置即可

System.arraycopy(a, 0, elementData, index, numNew);

從陣列a的下標為0的地方複製numNew個資料到elementData陣列的下標為index處

ArrayList中獲取某一資料的下標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資料不能呼叫方法,所以不能通過equals進行比較,所以分類討論, 
如果傳入的引數o是是null的話通過elementData[i]==null進行比較,否則通過o.equals(elementData[i])進行比較。 
如果遍歷過程中找到該資料,返回該資料下標,遍歷結束沒有找到返回-1 
注意indexOf是找到第一個相等的資料就直接返回下標了,如果想找到該資料在ArrayList中的最後一個位置,使用lastIndexOf即可

清空ArrayList的全部資料clear()

    public void clear() {

        modCount++;

        // clear to let GC do its work

        for (int i = 0; i < size; i++)

            elementData[i] = null;

        size = 0;

    }

首先modCount++,這是如果其他執行緒正在通過迭代器進行資料的訪問,檢測到modCount發生了變化將會丟擲ConcurrentModificationException 
然後設定陣列中的每一個元素為null,方便垃圾回收,最後設定size=0; 
雖然資料全部置null,size歸0,但是elementData的大小沒有變化

ArrayList的克隆clone()

    /**

     * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The

     * elements themselves are not copied.)

     *

     * @return a clone of this <tt>ArrayList</tt> instance

     */

    public Object clone() {

        try {

            ArrayList<?> v = (ArrayList<?>) super.clone();

            v.elementData = Arrays.copyOf(elementData, size);

            v.modCount = 0;

            return v;

        } catch (CloneNotSupportedException e) {

            // this shouldn't happen, since we are Cloneable

            throw new InternalError(e);

        }

    }

可以看到clone方法只是進行了淺克隆,在某些需要深克隆的場景出,需要自己負責每一個元素的深克隆

ArrayList刪除指定位置的資料

    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;

}

程式碼流程還是比較清晰 
下標檢查,modCount++達到迭代器快速失敗,陣列元素的移動,返回舊值 
其中比較重要的一點就是

elementData[–size] = null; // clear to let GC do its work

Effective Java提到了這點,自己申請記憶體自己要記得管理,否則造成記憶體洩露

ArrayList刪除某一個資料

    public boolean remove(Object o) {

        if (o == null) {

            for (int index = 0index < size; index++)

                if (elementData[index] == null) {

                    fastRemove(index);

                    return true;

                }

        } else {

            for (int index = 0index < size; index++)

                if (o.equals(elementData[index])) {

                    fastRemove(index);

                    return true;

                }

        }

        return false;

    }

這裡同樣是根據傳入的引數o是否為null進行分類處理,找到元素所在的位置index後直接呼叫 fastRemove(index)進行資料的刪除,fastRemove比remove不需要檢查陣列下標

ArrayList的排序sort

    public void sort(Comparator<? super E> c) {

        final int expectedModCount = modCount;

        Arrays.sort((E[]) elementData, 0, size, c);

        if (modCount != expectedModCount) {

            throw new ConcurrentModificationException();

        }

        modCount++;

 }

先儲存modCount,如果modCount在排序過程中被改變,丟擲ConcurrentModificationException異常 
排序是直接呼叫Arrays.sort方法

ArrayList的縮容trimToSize()

    public void trimToSize() {

        modCount++;

        if (size < elementData.length) {

            elementData = (size == 0)

              ? EMPTY_ELEMENTDATA

              : Arrays.copyOf(elementData, size);

        }

   }

先登記modCount++是迭代器可以快速失敗 
然後通過 Arrays.copyOf進行陣列建立和複製,使ArrayList的容量等於儲存的資料的數量的大小

 

相關文章