天天看點

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

一.首先來看一下ArrayList的類圖:

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

1,實作了RandomAccess接口,可以達到随機通路的效果。

2,實作了Serializable接口,可以用來序列化或者反序列化。

3,實作了List接口,是List的實作類之一

4,實作了Collection接口,是Collection家族的成員之一

5,實作了Iterable接口,代表可以對ArrayList進行For-each周遊。

二.然後咱們來看一下ArrayList的相關屬性:

1,Long serialVersionUID = 8683452581122892189L,ArrayList序列化的版本ID。

2,Int DEFAULT_CAPACITY = 10,預設的初始容量為10

3,Final Object[] EMPTY_ELEMENTDATA = {},用于空執行個體的共享空數組執行個體。(new ArrayList(0))

4,Final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {},用于提供預設大小的執行個體的共享空數組執行個體。(new ArrayList())

5,transient Object[] elementData。存儲ArrayList的數組緩沖區,ArrayList的容量是數組的長度。

6,Int size,ArrayList中元素的數量。

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

三.接着來看一下ArrayList的構造方法:

有參構造方法:很清晰的可以看出,如果initalCapacity>0,那麼就建立一個新的長度為initalCapacity的ArrayList,如果initakCapacity=0,就用空執行個體的共享空數組執行個體EMPTY_ELEMENTDATA。其他情況就抛出非法請求。

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

無參構造方法:也可以很清晰的看出,如果使用者不傳入初始容量,那麼ArrayList就會使将預設大小的執行個體的共享空數組執行個體指派給elementData。

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

帶集合參數的構造方法:

這也是将集合轉換為數組的一個方法。@param c,集合,代表集合中的元素都會被放到List當中。@throws 如果集合為空,就抛出空指針異常。為了防止c.toArray不正确的執行,導緻沒有傳回一個Object[],進行了相關的特殊處理。如果數組的大小等于0的話,那麼就将預設的數組空執行個體大小指派給elementData。

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

四.測試異常

那麼為什麼c.toArray會不傳回一個Object[].class呢?來咱們寫一些測試類,來測試一下。 

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

如果c.toArray一直會傳回Object[].class,那麼輸出的結果都會是java.lang.Object。但是測試結果如下圖。顯然從測試結果上,可以看出java.util.ArrayList會傳回一個Object對象,但是java.util.Arrays$ArrayList(Array的私有内部類ArrayList)卻傳回了String對象。這是為什麼呢?

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

翻看ArrayList的toArray方法,會發現使用了Array.copyOf方法。

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

那麼我們繼續往下走,看一下這個copyOf方法已經該方法的具體實作形式。

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

通過這個三元運算符,也能夠看出這一個複制的邏輯。如果newType是Object類型的話,那麼就傳回數組的類型為Object,如果不是的話,就是newType類型的。而我們在ArrayList的toArray方法裡面放入的elementData前面已經講解過是Object類型的,是以ArrayList必然就是一個Object類型。

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

看完ArrayList内部的toArray源碼之後,我們來看一下Array中的内部ArrayList的源碼:

隻截取了部分源碼,可以看出内置的ArrayList是直接把接收到的數組指派給a,然後通過toArray方法,直接把a的克隆傳回,而這是傳入的資料是什麼類型,傳回的就是什麼類型。是以,在我們上面的例子中,實際上傳回的是String類型的數組,再将其中的元素指派成Object類型的,自然報錯。

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)
資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

好,看完了ArrayList的屬性和構造方法,咱們來看一下ArrayList的相關方法。

五.添加元素

在清單的最後添加元素,同時在父類中的abstractList中有記錄modCount屬性,用來記錄數組修改的次數。

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

在指定位置添加指定的元素:

Index代表插入元素的位置,如果目前位置已經有了元素的話,那麼就将該元素和元素後面的所有元素向後移一位,可能會抛出IndexOutOfBoundsException。這時候就需要考慮擴容了。

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

而這兩個插入的方法還需要調用一些相關的私有方法。去計算當然的容量,保證ArrayList的容量健康,源碼放下面了,因為比較簡單,就不多說啦。

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

六.擴容機制

添加方法自然和擴容是分不開的。ArrayList自然也是有一套非常完善的擴容機制的,先前不是說了嗎,如果在添加元素的時候容量不足,自然就需要擴容了。

1,MAX_ARRAY_SIZE代表了整個數組最大可以配置設定到的size,一些虛拟機再數組中預留了一些header—words,如果想要嘗試配置設定更大的size,很有可能會報OOM的錯誤。

2,minCapacity:期望的最小容量,是以擴容一定要比這個數大。

3,最大容量傳回Inter.MAX_VALUE。

正常情況下,新容量是原來容量的1.5倍,如果原容量的1.5倍比minCapacity小,那麼就擴容到minCapacity,特殊情況擴容到Inter.MAX_VALUE

這也就解釋了為什麼為什麼空執行個體預設數組有的時候是EMPTY_ELEMENTDATA,而又有的時候是DEFAULTCAPACITY_EMPTY_ELEMENTDATA。New ArrayList()會将elementData指派為DEFAULTCAPACITY_EMPTY_ELEMENTDATA,new ArrayLIst(0),會将elementData指派為EMPTY_ELEMENTDATA。後者添加元素會擴容到容量為1,前者擴容之後容量為10。

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

 七.删除的方法

删除指定下面元素的方法

1,index:删除的指定下标

2,下标越界會抛出IndexOutOfBoundsException

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

删除指定元素的方法

如果存在,那麼删除傳回true,否則的話傳回false,o表示指定的元素

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

私有的移除方法:

私有的删除方法,跳過邊界檢查且不傳回移除的元素。

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

八.查找的方法

查找指定元素所在的位置

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

查找指定位置的元素

這個方法直接傳回elementData數組指定下标的元素,效率還是很高的,是以ArrayList的for循環周遊的效率還是很高的。

九.序列化方法

ArrayList是可以序列化和反序列化的,具體實作的方法如下:

将ArrayList的執行個體的狀态儲存到一個流裡面。

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

根據一個流重新生成一個ArrayList。根據序列化的方法可以看出,elementData之是以用transient修飾,是因為JDK不想将整個elementData都序列化或者反序列化,而隻是将size和實際存儲的元素進行序列化或者反序列化,進而節省空間和時間。

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

十.建立子數組

SubList的set()方法,是直接修改ArrayList中的elementData數組的,是以在使用的時候一定要注意,同時SubList是沒有實作Serializable接口的,是以是不能序列化的。

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)
資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

十一.疊代器

建立疊代器的方法,和Itr相關屬性,hasNext()方法和next方法,cursor表示下一個要傳回的元素的下标,lastRet表示最後一個元素的下标,沒有元素傳回-1,expectedModCount表示期望的count。

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)

在疊代的時候,會檢驗modCount是否等于expectedModCount,不等于的話就會抛出著名的ConcurrentModificationException異常。

資料結構——ArrayList的源碼分析(你所有的疑問,都會被解答)