筆記--ArrayList初始化&擴容

一、序
    ArrayList作爲常用的集合,頻繁的出現在工作和麪試中,今天咱們從源碼層面來複習一下有關ArrayList的一些知識。
 
二、
1.簡介
  ArrayList底層是數組隊列,可以動態的擴容,
它實現了java.io.Serializable接口,支持序列化,
它實現了Cloneable接口,可以被克隆,
它實現了 RandomAccess 接口,支持快速隨機訪問(根據下標獲取元素)。
ArrayList可以根據數組下標快速的讀取元素,他的查詢時間複雜度爲O(1),因此ArrayList適用於查詢頻繁的場景,反之它的插入刪除操作的時間複雜度爲O(n)。
 
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{...}
 
2.ArrayList是否線程安全?
    ArrayList是線程不安全的,在多線程操作同一個數組時,可能會導致數組下標越界,舉例:
List<String> list = new ArrayList<String>();
假設已經添加了9個元素,此時list的size = 9,
private static final int DEFAULT_CAPACITY = 10;//默認大小
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 
public boolean add(E e) {
    ensureCapacityInternal(size + 1); // Increments modCount!!
    //根據下標賦值
    elementData[size++] = e;
    return true;
}
 
private void ensureCapacityInternal(int minCapacity) {
    //判斷數組是否爲空,如果爲空最小擴容量爲默認大小10
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    //判斷是否需要擴容
    ensureExplicitCapacity(minCapacity);
}
 
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    //最小擴容量大於當前數組長度就需要擴容
    if (minCapacity - elementData.length > 0)
        //開始擴容方法
        grow(minCapacity);
}
 
thread_1 調用 list.add("a");走到ensureCapacityInternal(size + 1)方法時掛起,此時並不需要擴容;
thread_2 調用 list.add("b");發現數組不需要擴容,直接將"b"放在下標爲9的位置,此時size=10;
然後thread_1 繼續執行,嘗試將"a"放在下標爲10的位置,但是數組並沒有擴容,最大下標爲9,所以程序拋出數組下標越界異常。並且,elementData[size++] = e也不是原子操作,多線程操作時可能會導致值覆蓋的問題。
所以當多線程的情況下可以考慮使用Vector或者CopyOnWriteArrayList
 
3.ArrayList擴容機制
ArrayList的無參構造是創建一個空的數組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
這種方式創建一個ArrayList時,在第一次調用add()方法的時候就會進行一次擴容操作,在上面的ensureCapacityInternal()和ensureExplicitCapacity()方法中我們可以看到
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//最大容量
//擴容方法
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;//舊容量
    //位運算,新容量 = 舊容量 + 舊容量/2
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //判斷新容量是否小於最小所需容量,true -> 新容量 = 最小所需容量
    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);
}
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    //如果新容量超出最大容量,則新容量爲Interger.MAX_VALUE,否則爲MAX_ARRAY_SIZE
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}
所以在使用ArrayList時,最好可以給定一個初始值大小,這樣減少擴容的次數,提升性能
//指定初始大小的有參構造(還有一種是直接傳入一個Collection集合)
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);
    }
}
 
PS:再一個值的注意的就是在ArrayList源碼中頻繁的用到了Arrays.copyOf()和System.arraycopy()兩個方法,簡單說下兩種方式的區別:
    arraycopy()需要目標數組,將原數組拷貝到你自己定義的數組裏,而且可以選擇拷貝的起點和長度以及放入新數組中的位置
    copyOf()是系統自動在內部新建一個數組,並返回該數組。
其實在Arrays.copyOf()內部調用了System.arraycopy()方法。
4.小結
總體來說ArrayList的源碼還是比較容易看明白的,除去擴容方法其他的方法也是值的學習的,以上內容如果有不正確的地方歡迎大家指正批評。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章