1. 程式人生 > >資料結構——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的構造方法:

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

 

無參構造方法:也可以很清晰的看出,如果使用者不傳入初始容量,那麼ArrayList就會使將預設大小的例項的共享空陣列例項賦值給elementData。

帶集合引數的構造方法:

這也是將集合轉換為陣列的一個方法。@param c,集合,代表集合中的元素都會被放到List當中。@throws 如果集合為空,就丟擲空指標異常。為了防止c.toArray不正確的執行,導致沒有返回一個Object[],進行了相關的特殊處理。如果陣列的大小等於0的話,那麼就將預設的陣列空例項大小賦值給elementData。

 

四.測試異常

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

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

 

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

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

 

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

 

看完ArrayList內部的toArray原始碼之後,我們來看一下Array中的內部ArrayList的原始碼:

只截取了部分原始碼,可以看出內建的ArrayList是直接把接收到的陣列賦值給a,然後通過toArray方法,直接把a的克隆返回,而這是傳入的資料是什麼型別,返回的就是什麼型別。所以,在我們上面的例子中,實際上返回的是String型別的陣列,再將其中的元素賦值成Object型別的,自然報錯。

 

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

五.新增元素

在列表的最後新增元素,同時在父類中的abstractList中有記錄modCount屬性,用來記錄陣列修改的次數。

在指定位置新增指定的元素:

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

 

而這兩個插入的方法還需要呼叫一些相關的私有方法。去計算當然的容量,保證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。

 

 七.刪除的方法

刪除指定下面元素的方法

1,index:刪除的指定下標

2,下標越界會丟擲IndexOutOfBoundsException

 

刪除指定元素的方法

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

 

私有的移除方法:

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

 

 

 

八.查詢的方法

查詢指定元素所在的位置

查詢指定位置的元素

這個方法直接返回elementData陣列指定下標的元素,效率還是很高的,所以ArrayList的for迴圈遍歷的效率還是很高的。

九.序列化方法

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

將ArrayList的例項的狀態儲存到一個流裡面。

 

 

 

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

 

 

 

十.建立子陣列

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

 

 

十一.迭代器

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

 

在迭代的時候,會檢驗modCount是否等於expectedModCount,不等於的話就會丟擲著名的ConcurrentModificationException異常。

 

&n