筆記--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的源碼還是比較容易看明白的,除去擴容方法其他的方法也是值的學習的,以上內容如果有不正確的地方歡迎大家指正批評。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.