ArrayList 源碼分析

公衆號原文:ArrayList 源碼分析
博客原文:ArrayList 源碼分析
如下源碼分析使用的 Java 版本爲 1.8java

1. 概覽

ArrayList 是基於數組實現的,繼承 AbstractList, 實現了 List、RandomAccess、Cloneable、Serializable 接口,支持隨機訪問。設計模式

java.util public class ArrayList<E> extends AbstractList<E> 
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
複製代碼

2. Java Doc 關鍵點:

  • 實現List接口的動態數組,容量大小爲 capacity,默認的容量大小 10,會自動擴容
  • 可包含空元素 null
  • size, isEmpty, get, set, iterator, and listIterator 等操做的複雜度爲 O(1),The add operation runs in amortized constant time, that is, adding n elements requires O(n) time,其它操做爲線性時間
  • 非線程安全,多線程環境下必須在外部增長同步限制,或者使用包裝對象 List list = Collections.synchronizedList(new ArrayList(...));
  • 快速失敗:在使用迭代器時,調用迭代器的添加、修改、刪除方法,將拋出 ConcurrentModificationException 異常,可是快速失敗行爲不是硬保證的,只是盡最大努力

3. 成員屬性

當添加第一個元素時,elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的任何空ArrayList都將擴展爲默認的capacity數組

private static final int DEFAULT_CAPACITY = 10; // 默認容量大小
private static final Object[] EMPTY_ELEMENTDATA = {}; // ArrayList空實例共享的一個空數組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // ArrayList空實例共享的一個空數組,用於默認大小的空實例。與 EMPTY_ELEMENTDATA 分開,這樣就能夠了解當添加第一個元素時須要建立多大的空間
transient Object[] elementData; // 真正存儲ArrayList中的元素的數組
private int size;   // 存儲ArrayList的大小,注意不是elementData的長度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // 數組的最大長度
protected transient int modCount = 0; //AbstractList類的,表示 elementData在結構上被修改的次數,每次add或者remove它的值都會加1
複製代碼

4. 構造方法

// 無參構造方法,默認初始容量10
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_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);
    }
}
// 經過一個容器來初始化
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray(); 
    if ((size = elementData.length) != 0) { // c.toArray 返回的可能不是 Object[]
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        this.elementData = EMPTY_ELEMENTDATA; // replace with empty array.
    }
}
複製代碼

5. 添加元素與擴容

添加元素時使用 ensureCapacityInternal() 方法來保證容量足夠,size + 1 爲最少須要的空間大小,若是elementData的長度不夠時,須要使用 grow() 方法進行擴容安全

// 添加一個元素
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
// 計算最少須要的容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { 
        // 默認的空實例第一次添加元素時,使用默認的容量大小與minCapacity的最大值
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++; 
    if (minCapacity - elementData.length > 0) // 須要的容量大於elementData的長度
        grow(minCapacity);  // 進行擴容
}
複製代碼

擴容:當新容量小於等於 MAX_ARRAY_SIZE 時,新容量的大小爲 oldCapacity + (oldCapacity >> 1)minCapacity 之間的較大值 ,也就是舊容量的 1.5 倍與 minCapacity 之間的較大值bash

private void grow(int minCapacity) {
    int oldCapacity = elementData.length; // 本來的容量
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 新的容量
    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;
}
複製代碼

最後調用 Arrays.copyOf 複製原數組,將 elementData 賦值爲獲得的新數組。因爲數組複製代價較高,因此建議在建立 ArrayList 對象時就指定大概的容量大小,減小擴容操做的次數微信

public class Arrays {
    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) {
        @SuppressWarnings("unchecked")
        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;
    }
    //...
}
複製代碼

經過 addAll 添加一個集合中全部元素時的擴容:至少須要的容量爲兩個集合的長度之和,一樣是經過 ensureCapacityInternal() 來保證容量是足夠的,而後調用 System.arraycopy 將要添加的集合中的元素複製到原集合已有元素的後面多線程

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;
}
複製代碼

6. 刪除元素

刪除指定下標的元素時,若是下標沒有越界,則取出下標對應的值,而後將數組中該下標後面的元素都往前挪1位,須要挪的元素數量是 size - index - 1,時間複雜度爲 O(n),因此刪除元素的代價挺高併發

public E remove(int index) {
    rangeCheck(index); // 檢查下標是否在數組的長度範圍內
    modCount++;
    E oldValue = elementData(index); // 下標爲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;
}
private void rangeCheck(int index) {
    if (index >= size)  
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
複製代碼

刪除在指定集合中的全部元素 removeAll,刪除不在指定集合中的全部元素 retainAlldom

這二者都是經過 batchRemove 來批量刪除源碼分析

// 刪除在指定集合中的全部元素
public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);  // c 不能爲null
    return batchRemove(c, false);
}
// 刪除不在指定集合中的全部元素,也就是隻保留指定集合中的元素,其它的都刪除掉
public boolean retainAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, true);
}
// 批量刪除
private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;   // r爲當前下標,w爲當前須要保留的元素的數量(或者說是下一個需保留元素的下標)
    boolean modified = false;
    try {
        for (; r < size; r++)
            if (c.contains(elementData[r]) == complement)   // 判斷元素 elementData[r] 是否須要刪除
                elementData[w++] = elementData[r];
    } finally {
        // r != size 的狀況多是 c.contains() 拋出了異常,將 r 以後的元素複製到 w 以後
        if (r != size) { 
            System.arraycopy(elementData, r, elementData, w, size - r);
            w += size - r;
        }
        if (w != size) {
            // w 以後的元素設置爲 null 以讓 GC 回收
            for (int i = w; i < size; i++) 
                elementData[i] = null;  
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
    return modified;
}
複製代碼

刪除第一個值爲指定值的元素 remove(Object o),參數 o 能夠爲 null

fastRemove(int index)remove(int index) 幾乎同樣,只不過不返回被刪除的元素

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;
}
private void fastRemove(int index) {
    modCount++;
    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
}
複製代碼

7. 遍歷

ArrayList 支持三種方式:

  • for循環下標遍歷
  • 迭代器(Iterator和ListIterator)
  • foreach 語句

迭代器 Iterator 和 ListIterator 的主要區別:

ArrayList 的迭代器 Iterator 和 ListIterator 在《設計模式 | 迭代器模式及典型應用》這篇文章中有過詳細介紹,這裏只作一個小結

  • ListIterator 有 add() 方法,能夠向List中添加對象,而 Iterator 不能
  • ListIterator 和 Iterator 都有 hasNext() 和 next() 方法,能夠實現順序向後遍歷,可是 ListIterator 有 hasPrevious() 和 previous() 方法,能夠實現逆向(順序向前)遍歷。Iterator 就不能夠。
  • ListIterator 能夠定位當前的索引位置,nextIndex() 和 previousIndex() 能夠實現。Iterator 沒有此功能。
  • 均可實現刪除對象,可是 ListIterator 能夠實現對象的修改,set() 方法能夠實現。Iierator 僅能遍歷,不能修改

foreach 循環:

foreach 循環涉及到一個 Consumer 接口,接收一個泛型的參數T,當調用 accept 方法時,Stream流中將對 accept 的參數作一系列的操做

public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;  // 記錄當前的 modCount
    @SuppressWarnings("unchecked")
    final E[] elementData = (E[]) this.elementData;
    final int size = this.size;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        action.accept(elementData[i]);
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}
複製代碼

8. 序列化

ArrayList 有兩個屬性被 transient 關鍵字 修飾,transient 關鍵字 的做用:讓某些被修飾的成員屬性變量不被序列化

transient Object[] elementData;
protected transient int modCount = 0;
複製代碼

爲何最爲重要的數組元素要用 transient 修飾呢?

跟Java的序列化機制有關,這裏列出Java序列化機制的幾個要點:

  • 須要序列化的類必須實現java.io.Serializable接口,不然會拋出NotSerializableException異常
  • 若沒有顯示地聲明一個serialVersionUID變量,Java序列化機制會根據編譯時的class自動生成一個serialVersionUID做爲序列化版本比較(驗證一致性),若是檢測到反序列化後的類的serialVersionUID和對象二進制流的serialVersionUID不一樣,則會拋出異常
  • Java的序列化會將一個類包含的引用中全部的成員變量保存下來(深度複製),因此裏面的引用類型必須也要實現java.io.Serializable接口
  • 當某個字段被聲明爲transient後,默認序列化機制就會忽略該字段,反序列化後自動得到0或者null值
  • 靜態成員不參與序列化
  • 每一個類能夠實現readObject、writeObject方法實現本身的序列化策略,即便是transient修飾的成員變量也能夠手動調用ObjectOutputStream的writeInt等方法將這個成員變量序列化。
  • 任何一個readObject方法,不論是顯式的仍是默認的,它都會返回一個新建的實例,這個新建的實例不一樣於該類初始化時建立的實例
  • 每一個類能夠實現private Object readResolve()方法,在調用readObject方法以後,若是存在readResolve方法則自動調用該方法,readResolve將對readObject的結果進行處理,而最終readResolve的處理結果將做爲readObject的結果返回。readResolve的目的是保護性恢復對象,其最重要的應用就是保護性恢復單例、枚舉類型的對象

因此問題的答案是:ArrayList 不想用Java序列化機制的默認處理來序列化 elementData 數組,而是經過 readObject、writeObject 方法自定義序列化和反序列化策略。

問題又來了,爲何不用Java序列化機制的默認處理來序列化 elementData 數組呢

答案是由於效率問題,若是用默認處理來序列化的話,若是 elementData 的長度有100,可是實際只用了50,其實剩餘的50是能夠不用序列化的,這樣能夠提升序列化和反序列化的效率,節省空間。

如今來看 ArrayList 中自定義的序列化和反序列化策略

private static final long serialVersionUID = 8683452581122892189L;

private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
    int expectedModCount = modCount;
    s.defaultWriteObject(); // 默認的序列化策略,序列化其它的字段
    s.writeInt(size);   // 實際用的長度,而不是容量

    for (int i=0; i<size; i++) {  // 只序列化數組的前 size 個對象
        s.writeObject(elementData[i]);
    }

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

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

    if (size > 0) {
        int capacity = calculateCapacity(elementData, size);
        SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
        ensureCapacityInternal(size);

        Object[] a = elementData;
        for (int i=0; i<size; i++) {
            a[i] = s.readObject();
        }
    }
}

複製代碼

9. 快速失敗(fail-fast)

modCount 用來記錄 ArrayList 結構發生變化的次數,若是一個動做先後 modCount 的值不相等,說明 ArrayList 被其它線程修改了

若是在建立迭代器以後的任什麼時候候以任何方式修改了列表(增長、刪除、修改),除了經過迭代器本身的remove 或 add方法,迭代器將拋出 ConcurrentModificationException 異常

須要注意的是:這裏異常的拋出條件是檢測到 modCount != expectedmodCount,若是併發場景下一個線程修改了modCount值時另外一個線程又 "及時地" 修改了expectedmodCount值,則異常不會拋出。因此不能依賴於這個異常來檢測程序的正確性。

private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
    int expectedModCount = modCount;    // 記錄下當前的 modCount
    // 一些操做以後....
    if (modCount != expectedModCount) { // 比較如今與以前的 modCount,不相等表示在中間過程當中被修改了
        throw new ConcurrentModificationException();
    }
}

private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
    int expectedModCount = modCount;
    // 一些操做以後....
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

public void forEach(Consumer<? super E> action) {
    final int expectedModCount = modCount;
    // 一些操做以後....
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

public boolean removeIf(Predicate<? super E> filter) {
    final int expectedModCount = modCount;
    // 一些操做以後....
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

public void replaceAll(UnaryOperator<E> operator) {
    final int expectedModCount = modCount;
    // 一些操做以後....
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++; // 修改了要加一
}

public void sort(Comparator<? super E> c) {
    final int expectedModCount = modCount;
    // 一些操做以後....
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}

// 內部迭代器
private class Itr implements Iterator<E> {
    public void forEachRemaining(Consumer<? super E> consumer) {
        checkForComodification();
    }

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

後記

歡迎評論、轉發、分享,您的支持是我最大的動力

更多內容可訪問個人我的博客:laijianfeng.org

關注【小旋鋒】微信公衆號,及時接收博文推送

關注_小旋鋒_微信公衆號
相關文章
相關標籤/搜索