長文警告!!筆者嘔心瀝血之作,希望能幫到你
文章目錄
- 簡介
- 繼承關係
- 類中的屬性
- 構造函數
- 重頭戲 boolean add(E e)
- 確保容量足夠(達到需要的最小值)void ensureCapacityInternal(int minCapacity)
- 提升容量 void grow(int minCapacity)
- 情況一:使用無參構造,第一次add(E e):
- 情況二:newCapacity沒超過MAX_ARRAY_SIZE,正常擴容1.5倍
- 情況三:newCapacity超過MAX_ARRAY_SIZE,擴容至MAX_ARRAY_SIZE
- 其他add方法
- add總結
- 刪除元素
- remove(int index)刪除指定位置的元素
- remove(Object o)刪除指定的對象
- boolean removeAll(Collection<?> c)刪除當前list和傳入參數c的"交集"
- 其他方法
- 後記
簡介
底層使用對象數組Object[] elementData
實現,線程不安全,每次 刪除元素/擴容 需要把元素複製到新的數組,如果期間有兩個線程同時執行 複製 數組的操作,必然有一部分數據修改丟失。
繼承關係
實現的接口
- List接口:我們會出現這樣一個疑問,在查看了ArrayList的父類AbstractList都實現了List接口,而ArrayList又是AbstractList的子類,那爲什麼子類ArrayList還是去實現一遍呢?有的人說是爲了查看代碼方便,使觀看者一目瞭然,說法不一,但每一個讓我感覺合理的,但是在stackOverFlow中找到了答案,這裏其實很有趣。開發這個collection 的作者Josh說:這其實是一個mistake,因爲他寫這代碼的時候覺得這個會有用處,但是其實並沒什麼用,但因爲沒什麼影響,就一直留到了現在。原文出處
- RandomAccess接口:這個是一個標記性接口,通過查看api文檔,它的作用就是用來快速隨機存取,有關效率的問題,在實現了該接口的話,那麼使用普通的for循環來遍歷,性能更高,例如arrayList。
而沒有實現該接口的話,使用Iterator來迭代性能更高,例如linkedList。所以這個標記性只是爲了讓我們知道我們用什麼樣的方式去獲取數據性能更好。 - Cloneable接口:實現了該接口,就可以使用Object.Clone()方法了。
- Serializable接口:實現該序列化接口,表明該類可以被序列化,什麼是序列化?簡單的說,就是能夠從類變成字節流傳輸,然後還能從字節流變成原來的類。
類中的屬性
值得注意的是,ArrayList 底層用於存儲元素的Object[] elementData
是聲明爲transient的
這麼做的的目的是在進行序列化時,並不會把整個數組序列化,而是隻把size
大小的元素序列化,會把爲null的部分捨去。這個做的好處是減少序列化的元素,提升效率和節省空間。另外,IO操作是費時的。一般情況下,elementData
默認容量是10,在增加元素時如果容量不夠,擴容會提升1.5倍,所以elementData
中有可能會有大量的空位出現。
構造函數
無參構造ArrayList()
可以看到註釋裏面說,初始化的是一個容量爲 10 的空 list ,但是之前就已經提到了this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
中的DEFAULTCAPACITY_EMPTY_ELEMENTDATA
是個空的對象數組。那又何來容量爲10呢?
再看這兩個變量,一切就說得通了:
原來this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
就是一個標記,標記初始化ArrayList時使用的是無參構造,至於elementData
容量變成10則推遲到添加第一個元素時才執行。
這麼做的好處是顯而易見的,如果我們只是無參構造一個ArrayList而沒有使用他,那麼底層的elementData
只是Object[] elementData= {};
這樣能節省空間和操作的步驟,提升效率。類似的思想就像是單例模式的懶漢式和餓漢式
指定容量 ArrayList(int initialCapacity)
這個很簡單,註釋的翻譯筆者已經用紅字寫在圖裏
很少用到的ArrayList(Collection<? extends E> c)
可能有人會有疑問按順序返回是怎麼回事,筆者做了以下實驗:
Stack按順序返回的是123,
使用pop返回的是321,
重頭戲 boolean add(E e)
表面上看,只需要確保動態數組elementData裝得下
然後把他用elementData[size++] = e
裝下就可以了
但是其實要確保裝得下並不是很容易的事情
確保容量足夠(達到需要的最小值)void ensureCapacityInternal(int minCapacity)
這裏minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
爲什麼要取最小值呢?
原來當以使用無參構造之後用addAll(c)
的時候可能傳入的參數就會大於10,最終elementData.length
小於size + numNew
的話就容量就不夠了。
提升容量 void grow(int minCapacity)
容量提升默認是1.5倍,但並非絕對着這樣
情況一:使用無參構造,第一次add(E e):
筆者在Debug模式下觀測得到,這種情況下,elementData
最終倍初始化爲長度爲10的對象數組Object[10]
情況二:newCapacity沒超過MAX_ARRAY_SIZE,正常擴容1.5倍
可以看到使用了數組複製,如果頻繁擴容會效率不高,尤其是數據量比較大時。但是數據量比較大時,一次擴容也會比較大,並不容易頻繁填滿導致頻繁擴容
情況三:newCapacity超過MAX_ARRAY_SIZE,擴容至MAX_ARRAY_SIZE
其中MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
,如果需要,所以還可以再次擴容:
自此,再次擴容就會拋出OutOfMemoryError
錯誤了,因爲Integer.MAX_VALUE + 1
= -1,不是我瞎說,代碼爲證:
具體爲什麼是這樣的呢?筆者又打開了Integer
的源碼進行了分析:
0x7fffffff + 1
=0x80000000
說深入了就跟計算機表示整數使用的是補碼有關,想了解的同學可以去學習一下補碼
其他add方法
以add(int index, E element)
爲例,非常地樸實無華且枯燥↓
其他add方法都比較簡單,不再贅述
add總結
如果使用new ArrayList();
,elementData
初始爲null,在第一次add時才初始化。
如果用循環add(e)
添加元素,每次需要擴容時,通過位運算實現容量擴容1.5倍,因爲是位運算,所以15擴容後是22。
如果使用addAll(c)
的時候,有可能擴容1.5倍也不夠,那麼會直接擴容到剛好能裝下的容量。
刪除元素
remove(int index)刪除指定位置的元素
可能有部分讀者對
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
這一句有疑問,筆者翻月了System
的源碼發現
顯然,這行代碼的意思就是把原來index + 1
到末尾的元素複製到index
到原來末尾-1的位置。
至於rangeCheckForAdd(int index)
,不是瞎子應該都能讀懂↓
remove(Object o)刪除指定的對象
fastRemove(int index)
和remove(int index)
的區別只是:
- 私有
- 沒有返回值
- 不要判斷index是否超出
size
boolean removeAll(Collection<?> c)刪除當前list和傳入參數c的"交集"
這個方法會刪除arrsyList
中所有在c
中出現過的元素
batchRemove(Collection<?> c, boolean complement) 真正刪除元素
此處的疑問主要有二:
- 爲什麼
final Object[] elementData = this.elementData;
會聲明爲 final 類型 - 調用
AbstractCollection
中的contains(Object o)
會拋出異常嗎?
解答:
第一個問題:
首先,Object[] elementData
是一個引用類型變量,這個引用的地址的值不能修改,但是這個引用所指向的對象裏面的內容還是可以改變的。也就是elementData[1]
指向的對象可以改變。所以後續的一系列操作都是沒有問題的。
其次,定義爲 final 類型之後不會在後續的操作中發生操作了其他的 elementData
裏面的數據的情況,保障操作的都是this.elementData
裏面的數據。
第二個問題:
在AbstractCollection
中找到contains(Object o)
函數並不會拋出異常!
但是有可能會因爲別的原因發生異常,註釋裏面說的是“保持與AbstractCollection
的行爲兼容性”,在我看來,應該是一種保險的寫法。
其他方法
剩餘的方法比較樸實無華且枯燥,直接看圖:
set(int index, E element)
get(int index)
indexOf(Object o)
後記
筆者最近在做源碼分析,簡單地概括了一下List的主要後代的族譜和特性: