List集合基礎增強底層源碼分析
作者:Stanley 羅昊
【轉載請注明出處和署名,謝謝!】
集合分為三個系列,分別為:List、set、map
List系列
特點:元素有序可重復
有序指的是元素的添加順序,也就是說,元素被第一個存進去的時候,它就在第一位,這就是list集合的元素添加順序;
通常情況下我們所說的有序有兩個概念,第一個是添加順序,第二個是大小順序(實際上就是元素值的大小)
List下面重點關注兩個實現類分別是:
ArrayList LinkedList
ArrayList
ArrayList底層實現是數組,既然是數組,那它就必然有數組的特點:查找快,增刪慢;
這里簡單解釋一下:
首先我們建立一個數組,一旦建立一個數組,那么程序就會在內存中開辟一個連續的內存存儲空間,並且它是有下標的,從0開始,一旦定義長度就無法發生改變。
現在,假設我們往數組里面存值,當然是你定義什么類型,你就存什么類型的值進去,如果現在想獲取,我們直接通過下標就可以進行獲取了,但是我刪除元素的時候,是怎么刪除的呢?
數組刪除過程
假設我定義一個數組如下圖:
紅框內,代表我存的值,黑線上方則是他們值對應的下標
假設我現在想刪除c這個元素,這個時候d就會向前移動e也會移到d的位置f也會移到e的位置上,結果后面會空出來的那個就被刪掉了,結果就成了👇下面這張圖B-1:
這個就是數組的刪除過程。
數組添加過程
剛講了刪除過程,現在我們就來看看數組的添加過程:
添加元素首先會給即將進來的這個元素分配一個空間在如圖B-1f后面,如果你想添加到如圖B-1下標為1的空間里,那么它就會開始依次向后移,b占領d的位置d占領e的位置f占領后面新開辟空間的位置上。
這就是它的增加與刪除過程,所以它增刪慢,查詢快;
那既然我剛講了,數組一旦定義,長度不能發生改變,那么在咱們List集合底層是數組實現的,你List集合定義的時候你給過長度嗎?很顯然並沒有給長度,那就可以無限添加元素,你填100個也行,你添10k也無所謂,既然數組長度不變,你說它底層怎么搞的呢?接下來我們就定義一個數組並且查看它的底層代碼。
List集合底層源碼詳解
我用的是IDEA,按兩下Shift鍵輸入ArrayList回車即可查看源碼,首先剛進來我們看到的是版本號:
private static final long serialVersionUID = 8683452581122892189L;//版本號
/**
* Default initial capacity.(默認初始容量,也就說,當我們定義一個List集合沒有給他指定長度的時候,默認長度就是10)
*/
private static final int DEFAULT_CAPACITY = 10;
那如果我添加11元素的時候怎么辦?
答:擴容,下面我會詳講擴容
剛開始建這個集合的時候,默認長度是10
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//你現在數組的長度
int newCapacity = oldCapacity + (oldCapacity >> 1);//新數組的長度 = oldCapacity + (oldCapacity>> 1)其中 >>符號是右移符,也就是/2的意思,算出來的結果是1.5,所以它擴容的時候是1.5倍1.5倍的擴的
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)(按住 Ctrl鍵 點進去 MAX_ARRAY_SIZE 就可以查看數組的最大容量,下面我有詳講)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
上面這個源碼,就是數組初始化與擴容的方法,也就是說ArrayList里面它數組初始化是在你添加第一個元素的時候,當它擴完容后接下來就是數組的拷貝
你在創建List集合的時候,你也可以給它傳一個初始容量,如何操作請繼續向下閱讀。
在ArrayList集合底層源碼里有一個有參構造跟無參構造,它們分別在什么時候使用呢?
首先先看一下有參構造:
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
什么時候用有參構造:當你向集合中添加元素數量比較多的時候,你最好給它指定初始容量,因為這樣就可以減少很多次的擴容和元素拷貝。
原理:如果你10000個元素,你讓它10個以上擴一次容,擴完容后你再讓它連續擴容必然會造成元素拷貝,元素拷貝它的性能就高不了,這樣如此反復的話必然會拉低它的性能,如果你元素過多的話,你給它指定初始容量,這樣我就能減少了很多次擴容和元素拷貝,從而提升性能(在一定程度上提升性能),
當元素比較少的時候,你就可以默認不指定初始容量
ArrayList容量有上限嗎?
元素是有上限的,它的容量是Integer的最大值,在源碼里面可以清楚的看到它的設定:
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;(寫的很清楚,它的大小不能超過Integer的最大值(21億))
我們可以看到它減了一個8,因為到后期就已經不需要擴容了,最后幾個元素它是一個一個添加的,添加一個擴一個容,添加一個擴一個容。
值得一提的是,當它的容量超過10億后,性能就有所衰減,這個實驗是有人做過的,也被證實了,10億之前它的性能都表現良好。
今日感悟:
很多人辭職的時候都會被老板挽留,誤認以為自己是公司里不可取代的人,可實際上,沒有人的工作是不可取代的。
老板在你辭職的時候挽留你,不過是說明:你是在可以取代你的那群人中,價格最便宜的。
---恢復內容結束---