【Java源碼分析】ArrayList源碼分析

類的定義

public class ArrayList<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {}
  1. List接口的實現類,AbstractList 的子類,支持隨機訪問,因此底層實現的數據結構是數組
  2. 實現了所有list的操作,允許所有類型的元素,包括NULL
  3. 提供計算Array大小的接口,可以看做是一個Vector,區別在於Vector是線程安全的,ArrayList不是
  4. 由於底層實現數據結構是數組,所以get set iterator遍歷, 以及size判斷等都是常數時間。其他操作是線性時間複雜度,而且常數因子比LinkedList要小,因此效率略高
  5. 含有一個容量capacity實例,表示容量的大小,隨着數據的加入,該實例會自增,自增
  6. 非線程安全,因此如果在多線程環境下需要自行處理線程安全問題,也可以使用Collections.synchronizedList方法List list = Collections.synchronizedList(new ArrayList(...));進行包裝,而且最好是在聲明變量的時候就考慮是否需要做多線程的處理,避免出錯
  7. ConcurrentModificationException異常,如果ArrayList的實例在使用迭代器Iterator遍歷的時候,如果出現了不是使用這個迭代器所做的remove或者add操作,那麼就會直接拋出併發修改的異常。這個異常即使是在單線程下也是會出現的,常見的場景是用迭代器一邊遍歷一邊修改ArrayList對象的時候
  8. 對於7中的現象也叫做是fail-fast,該行爲只能儘量檢測出併發異常,但是不能做到絕對的線程安全,所以編程的時候不要因爲沒有拋出該異常就覺得是線程安全的

構造函數
三種類型,默認構造(默認構造的Listd的size 爲10),傳遞size的構造,傳遞一個Collection對象的構造。

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

public ArrayList() {
    this(10);
}

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    size = elementData.length;
    // c.toArray might (incorrectly) not return Object[] (see 6260652)
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, size, Object[].class);
}

擴容方式

private transient Object[] elementData; // 對象數組,實際存放數據的一個數組容器,默認初始10個Object
private int size; // 實際存放對象數目
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // 最大容量;由於一些虛擬機會在Array首部存儲一些信息,所以此處減去8字節 

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倍,如果指定值minCapacity比原來容量的1.5倍大,那麼按指定值擴容;如果指定值minCapacity比閾值MAX_ARRAY_SIZE大,那麼按如下策略確定擴容最終大小(minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;,這樣會存在一定的OOM風險

減少容量,用於減少一個ArrayList實例所佔存儲空間

public void trimToSize() {
    modCount++;
    int oldCapacity = elementData.length;
    if (size < oldCapacity) {
        elementData = Arrays.copyOf(elementData, size);
    }
}

從如下的一個側面說明ArrayList是允許空的對象存入的。

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

連接集合類Collection和線性表Array類的橋樑,該方法將集合中所存儲的對象拷貝到一個對象數組中返回

public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}

該方法還有另外一個多態實現,根據運行時類型返回指定類型的數組

public <T> T[] toArray(T[] a) { ... }

添加一個元素到集合

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

這個是添加到指定位置的,所以比較麻煩,需要拷貝移動。還有一個添加操作是直接添加到尾部,public boolean add(E e) { ... }只需要做一次賦值即可

刪除指定位置的元素

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; // Let gc do its work

    return oldValue;
}

從代碼來看也是比較好懂的,刪除指定位置元素,然後指定位置之後的元素前移,這是一個數組拷貝過程。但是需要注意最後一點,就是將最後一個空出的位置置空,這個問題在Effective Java中有提到過,就是及時的消除過期引用。對於一些自定義的數組類如果對象被移走了,但是指針沒有置爲空,那麼強引用還是會一直保持,就會導致內存泄露。

另一個刪除操作是刪除指定對象

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

其中用到了一個私有的刪除操作fastRemove(index),該操作的特點是不檢查邊界也不返回被刪除值,畢竟是自己內部調用,已經可以保證邊界是正確的。

清空操作

public void clear() {
    modCount++;

    // Let gc do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
}

比較直接,循環置空,然後將對象的清除工作交給GC,注意clear只是將內容清空,集合容器還是在的、

刪除集合中某些元素

public boolean removeAll(Collection<?> c) {
    return batchRemove(c, false);
}

功能就是將所有位於c中的元素全部刪除

保留集合中某些元素

public boolean retainAll(Collection<?> c) {
    return batchRemove(c, true);
}

功能就是將所有位於c中的元素全部保留,不在c中的元素全部刪除
對比這兩個方法就可以看出,實際的工作都是由private boolean batchRemove(Collection<?> c, boolean complement) {}完成的,當第二個參數是true的時候代表保留元素,否則代表刪除。

集合數據的狀態保存和恢復

private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();

    // Write out array length
    s.writeInt(elementData.length);

    // Write out all elements in the proper order.
    for (int i=0; i<size; i++)
        s.writeObject(elementData[i]);

    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }

}

恢復,將數據從流中取出

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in array length and allocate array
    int arrayLength = s.readInt();
    Object[] a = elementData = new Object[arrayLength];

    // Read in all elements in the proper order.
    for (int i=0; i<size; i++)
        a[i] = s.readObject();
}  

類似於序列化和反序列化,這裏狀態保存和恢復也是成對出現的,保存和恢復的順序都是一樣的,否則不可能得到正確的對象或者集合。注意在保存狀態的時候同樣是不能夠修改集合的,否則也是拋出併發修改異常

再來看看ArrayList的迭代器

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

前面說迭代器的時候提到過在使用同一個迭代器迭代訪問元素的時候,是不能修改集合的結構的(也就是大小,但是內容變化大小不變還是可以的比如add(index, value)或者set(index, value)是可以的,但是add(value)不可以,該操作會modCount++),願意就在於每當初始化一個迭代器實例的時候,該迭代器都保存了當前集合的修改次數。迭代器的每一個操作結束之後都會檢查保存的值是否和集合的modCount值相同,如果不同直接拋出異常並且結束,這就是所謂的fail-fast

獲取子集合操作

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}

返回從fromIndex到toIndex之間的元素構成的子集合,不包括toIndex。當toIndex和fromIndex相等的時候返回的是空的子集合而不是空指針

注意SubListprivate class SubList extends AbstractList<E> implements RandomAccess {}是ArrayList的一個內部子類,該子類實現了和ArrayList一樣的方法.

最後補充一下經常調用的連個拷貝函數Arrays.copyOf(elementData, newCapacity)System.arraycopy(elementData, index, elementData, index + 1, size - index);

前者最終調用了Arrays的copyOf()方法

public static <T> T[] copyOf(T[] original, int newLength) {
    return (T[]) copyOf(original, newLength, original.getClass());
}  

該方法實現如下

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

實現的過程是先確定泛型類型,然後調用系統的copy函數,將指定集合中的元素拷貝到目標集合中,目標集合是一個新建的newLength大小的集合。在實際的拷貝過程中還是調用了System.arraycopy()函數

是一個Native方法

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

該方法將srcPos+length-1個元素拷貝到dest中從destPos到destPos+length-1。由於是Native方法,所以在JDK中是看不到的,但是可以在openJDK中查看源碼。該函數實際上最終調用了C語言的memmove()函數,因此它可以保證同一個數組內元素的正確複製和移動,比一般的複製方法的實現效率要高很多,很適合用來批量處理數組。Java強烈推薦在複製大量數組元素時用該方法,以取得更高的效率。

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