java中集合是重要的部分之一,在實際工作過程也被高頻率使用,這裏大概記錄一下1.8中的ArrayList,主要分析一下幾個常用的方法,擴容機制,以及多線程下引起的線程安全問題,純屬個人拙見,不足之處還請評論支出,共同學習進步!
1.對於集合整體架構如下圖所示:
通過源碼我們可以看到ArrayLis的底層數據結構是數組,這也賦予了它檢索效率高的優勢,但是由於數組的侷限性,達到一定容量時需要擴容,爲了減少擴容帶來性能降低,我們在使用ArrayList初始化時可以根據使用場景預估其長度而定一個初始容量值,這樣可以減少頻繁擴容而帶來不必要的開銷。
2.常用方法
構造方法:
通過註釋我們可以看出來三個構造方法適用於不同的場景,
/**
* Constructs an empty list with the specified initial capacity.
* 初始容量值的構造方法
*/
public ArrayList(int initialCapacity);
/**
* Constructs an empty list with an initial capacity of ten.
* 無參構造方法,初始容量爲10
*/
public ArrayList();
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
* 構建一個包含具體集合元素的構造方法,它們的順序依次通過集合的迭代器返回
*/
public ArrayList(Collection<? extends E> c);
添加元素方法:
- 一個參數的add方法
/**
* Appends the specified element to the end of this list.
* 在list的末尾增加元素e
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
我們先來說說ArrayList的兩個重要參數,如註釋
/** * Shared empty array instance used for empty instances. * 用於空實例的空數組實例 */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * 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. * 用於默認大小空實例的空數組實例,我們將此與EMPTY_ELEMENTDATA區別開來,以瞭解添加第一個元素時需要擴容多少。 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
我們繼續跟進add方法
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
// 操作數記錄變量,Java快速報錯機制後續分析
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
calculateCapacity這個方法就利用瞭如上的兩個參數,如果是使用默認無參構造方法也就是DEFAULTCAPACITY_EMPTY_ELEMENTDATA數組對象時,增加第一個元素E e返回的是默認的容量長度DEFAULT_CAPACITY,在進入ensureExplicitCapacity方法後,經過if判斷後進行擴容,執行grow方法,我們繼續看grow方法
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
* 增加容量以確保它至少可以容納最小容量參數指定的元素數量。簡而言之就是擴容
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 右移1位操作,等效於oldCapacity/2,但是位移是底層操作更高效
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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
對於擴容有幾種情況:
- 如果使用默認構造方法,增加第一個元素時minCapacity = 10,經過grow方法後數組elementData從0擴容到10,後續再增加元素擴容時則是1.5倍的擴容
- 如果使用初始值構造方法,但是初始容量爲0時,增加第一個元素minCapacity=1,經過grow方法後數組elementData從,0擴容到1,在第5次添加數據進行擴容的時候纔是按照當前容量的1.5倍進行擴容
- 當擴容量(newCapacity)大於ArrayList數組定義的最大值後會調用hugeCapacity來進行判斷。如果minCapacity已經大於Integer的最大值(溢出爲負數)那麼拋出OutOfMemoryError異常
至此ArrayList擴容機制分析完畢,擴容完成後,添加新元素E e。
在ensureExplicitCapacity方法中有個一個增量modCount,這裏是利用了java的快速報錯機制,也算是一種保護機制,能夠防止多個進程同時修改同一個容器的內容。如果迭代遍歷比如使用迭代器Iterator(ListIterator)或者forEach,會將modCount變量值傳給exceptedModCount,並且會檢查modCount與exceptedModCount是否相等,如果此時對同一個容器執行新增刪除或者修改操作,modCount會改變,二者不相等則會拋出ConcurrentModificationException異常。但是如果使用Iterator的remove方法則不會發生此異常。舉例如下:
剩下的三個添加元素的方法在此不再贅述。
此篇主要內容到此結束,有不足之處還請大家指正,謝謝!