從JDK源碼學習Arraylist

  • 2020 年 4 月 10 日
  • 筆記

從今天開始從源碼去學習一些Java的常用數據結構,打好基礎:)

Arraylist源碼閱讀:

jdk版本:1.8.0

首先看其構造方法:

構造方法一:

第一種支援初始化容量大小,其中聲明一個對象數組,賦值給this.elementdata

構造方法二:

第二種無參構造函數,即不指定初始容量大小,則默認賦值this.elementdata為一個空的對象數組,但是由注釋可以看到其無參構造實際上初始容量為10

 

在elementData的注釋中也說了該變數是實際存儲Arrylist數據的存儲結構,任何空的arraylist,當第一次被調用add放進元素時,將會擴充容量為default_capacity也就是10

 

 

 

 

看看其add方法,因為arraylist也是有序的,因此加入的元素在列表尾部,在添加元素之前,調用ensureCapacityInternal,確保內部容量大小

在ensureCapacityInternal中將判斷當前的elementdata的值是否為空數組,若為空則賦值minCapacity為默認容量和入口參數minCapacity的較大值,然後進一步調用ensureExplicitCapacity明確容量大小

 

在ensureExplicitCapacity中,modCount自增,判斷當前最小容量和arraylist的實際元素個數差值若大於零,則調用grow函數來進行實際的容量擴充

 

 

擴容函數grow先取到當前arraylist的實際長度,然後將其擴大1.5倍,然後判斷該值和最小容量的大小,若擴充1.5倍小於所需要的最小容量,則賦值新的容量為需要的最小容量,此時並判斷是否產生溢出情況,也就是注釋裡面的overflow conscious mode的含義,所以arraylist不是無限擴容,看下其max_array_size的值

 

數組最大值為integer.max_value-8,也就是2的31次-1-8

至於為什麼要-8,這裡有些vm要存儲其最大值的大小需要八個位元組,如下圖所示

 

 如果擴充的新容量比max還大,則調用hugeCapacity,判斷最小的容量和2的31次-1的大小,若大於則賦值max_value,否則說明此時最小容量介於max_value-8和max_value之間,則賦值為max_value-8

 

然後調用Array.copyof將舊的arraylist中的值拷貝到新的擴充後的arraylist中,所以默認空數組的add操作後容量即為10

構造方法三:

可以傳遞任何實現了Collection介面的類,其調用collection的toarray方法返回一個對象數組,也就是將集合中的元素以對象數組形式返回,toarray的注釋里也說明了這個方法是array和collection的橋樑

 

 

 為了防止重寫toArray方法返回的並不是對象數組,因此這裡判斷一下elementData的類是否是對象數組,如果不是的話,則將element中的數組copy到對象數組中

比如有MySubClass是MyClass的子類。  Collection<MyClass> myCollection;  //myCollection里有很多元素。  Collection<MySubClass> mySubCollection;  //mySubCollection里有很多元素。  ArrayList<MyClass> myList = new ArrayList<MyClass>(myCollection);  也可以:  ArrayList<MyClass> myList = new ArrayList<MyClass>(mySubCollection);

意思就是這裡用extends e,來指定定義一個父類的arraylist,則其所有子類的集合都能放進該父類的arraylist,從而編譯器才能夠知道放入的元素都是滿足?也就是,初始定義arraylist的類型聲明

關於執行緒安全:

 上面遺留了一個modcount++的自增操作的解釋,看一下jdk對modcount的解釋

 該參數是對arraylist容量大小修改的次數,也就是刪減元素改變大小時可能會使正常的迭代過程出現錯誤,那麼針對單執行緒而言,不存在又讀又寫,但在多執行緒情況下,可能存在讀寫同時進行的操作,參考知乎一個很精簡明確的答案,看完真的是一目了然,如果結構發生變化則拋出ConcurrentModificationException

 

 通過調用上面這個方法來判斷是否結構發生變化,調用add remove時都將修改modcount,通過迭代時先保存一份modcount,若迭代過程中再取modcount和保存的值不等則拋出異常

總結:

①.初始不指定容量時設置為10

②.每次擴充為實際長度的1.5倍與所需最小容量比較

③.arraylist是非執行緒安全的

④.其最大值為2的31次-1

⑤.為避免連續擴容消耗記憶體,能初始化容量大小盡量指定容量

⑥.為啥會非執行緒安全,因為方法內部並非原子操作

 

參考:

https://zhuanlan.zhihu.com/p/72296421  hashmap

https://zhuanlan.zhihu.com/p/73283922  linkedhashmap

https://zhuanlan.zhihu.com/p/72463637 hashset

https://zhuanlan.zhihu.com/p/72156592 arraylist

https://www.jianshu.com/p/f174d49b391c

https://www.cnblogs.com/LiaHon/p/11089988.html 

https://blog.csdn.net/u012859681/article/details/78206494 執行緒安全問題