JDK源碼之 ArrayList
作者丨喬二爺?
來源丨喬二爺(hellozhouq)
00 前言
ArrayList 是我們?nèi)粘i_發(fā)中使用非常頻繁的一個集合,今兒我們通過源碼來看看它底層是怎么來實現(xiàn)的,了解了解它的優(yōu)缺點(diǎn)和真正適合的場景。01 內(nèi)部的組成
//默認(rèn)容量privatestaticfinalint DEFAULT_CAPACITY = 10;// 用于空實例的共享空數(shù)組實例。在初始化時,你傳進(jìn)去的初始化大小為0,那么就用這個來做的處理。privatestaticfinalObject[] EMPTY_ELEMENTDATA = {};//共享的空數(shù)組實例,用于默認(rèn)大小的空實例,默認(rèn)構(gòu)造器使用的。privatestaticfinalObject[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};// 用來存儲元素的數(shù)組transientObject[] elementData;//數(shù)組的大小privateint size;
02 構(gòu)造函數(shù)
先來看ArrayList 的構(gòu)造函數(shù)。
如果我們直接 new ArrayList() 使用默認(rèn)的構(gòu)造函數(shù)來創(chuàng)建它,那么它會直接使用一個空的 Object [],初始大小是0,它有一個默認(rèn)的初始化大小參數(shù) DEFAULT_CAPACITY 是 10,當(dāng)添加第一個元素時,這個空數(shù)據(jù)的大小才是10,我們也可以認(rèn)為它給我們創(chuàng)建的默認(rèn)的數(shù)據(jù)大小就是10 。平常我們一般在使用 ArrayList 的時候,會使用另外一個構(gòu)造函數(shù),可以帶初始容量進(jìn)去的。一般來說是可以預(yù)估出來數(shù)據(jù)的大小,比如幾十個,一百個,那么我們在初始化的時候就把這個數(shù)字帶進(jìn)去,避免數(shù)據(jù)容量太小在后續(xù)插入數(shù)據(jù)的時候進(jìn)行頻繁的擴(kuò)容,創(chuàng)建新的數(shù)組。擴(kuò)容這塊放在后面說,先看它里面比較核心的幾個方法。03 add() 方法
add() 方法很簡單,先去校驗需不需要擴(kuò)容,然后把數(shù)據(jù)的 size 位置設(shè)置為元素,最后返回true;舉個例子,比如我現(xiàn)在有這么一段代碼需要往數(shù)據(jù)中插入3個元素:List<String> list = newArrayList<>();list.add("張三");list.add("李四");list.add("王二");
04 set() 方法
如果你現(xiàn)在進(jìn)行 set(3,"麻子") ,首先先進(jìn)行 rangeCheck(index),檢查給的索引是否越界,此時數(shù)組的個數(shù)是3,但是索引下標(biāo)還是2,沒有索引為 3的 這個位置,就會判定為越界。如果是 set(1,"麻子") 是沒問題的,E oldValue = elementData(index) 這段代碼會先把索引為 1 這個位置的數(shù)據(jù) “李四” 取出來,那么 oldValue = “李四”;然后把 elementData[1] = “麻子”,此時 elementData = ["張三",“麻子”,“王二”],最后把 oldValue 的數(shù)據(jù) “李四” 返回。05 add(index,element) 方法
比如我們現(xiàn)在要在下標(biāo)為 1 的位置添加“光頭強(qiáng)”這條數(shù)據(jù)list.add(1,"光頭強(qiáng)");
System.arraycopy(elementData, index, elementData, index + 1, size - index);
System.arraycopy(elementData, 1, elementData, 2, 2);
06 get() 方法
get() 方法應(yīng)該是這里面最簡單的了,首先先校驗index 是否越界,然后直接調(diào)用 elementData(index) 方法,這個方法就是調(diào)用數(shù)組的取值。由于 elementData[index] 是直接通過數(shù)組直接定位這個元素,然后直接獲取這個元素的,這個也是ArrayList 性能最好的一個操作,優(yōu)點(diǎn)。
07 remove(index) 方法
remove() 方法首先還是會校驗索引是否越界,然后取出索引位置的數(shù)據(jù),然后根據(jù)刪除位置來計算需要移動元素的數(shù)量,如果移動需要移動元素的數(shù)量大于0 ,則使用 System.arraycopy 進(jìn)行元素的移動,然后再把最后一個元素設(shè)置為 null,最終返回 刪除的元素。例如 我們要刪除 index = 1 的元素 ,remove(1),當(dāng)前數(shù)組的長度 size 是 4 , oldValue = elementData(1) = "光頭強(qiáng)"。numMoved = size - index - 1,numMoved = 4 - 1 -1 = 2,相當(dāng)于要把后面的 “麻子”,“王二” 都要往前面移動一位。接下來是 System.arraycopy(elementData, index + 1, elementData, index, numMoved) ,轉(zhuǎn)換過來則為 System.arraycopy(elementData, 2, elementData, 1, 2)。意思是說把 elementData 數(shù)組中,從 index = 2 的位置開始的元素,一共有 2 個,拷貝到 elementData (還是原來的數(shù)組),從 index = 1 開始,進(jìn)行拷貝。拷貝前的 elementData = ["張三","光頭強(qiáng)","麻子","王二"]拷貝后的 elementData = ["張三","麻子","王二","王二"]
08?數(shù)組擴(kuò)容
ArrayList 里面最關(guān)鍵的一塊兒,就是數(shù)據(jù)裝滿以后如何進(jìn)行擴(kuò)容的。假設(shè)說我們現(xiàn)在用的數(shù)組是默認(rèn)的大小,也就是10 ,現(xiàn)在數(shù)據(jù)里面已經(jīng)裝滿了10個元素,此時數(shù)組的size =10,capacity = 10。此時我們要調(diào)用 add() 方法 插入一個元素,插入第11個元素的時候是不行的,因為數(shù)據(jù)已經(jīng)滿了,此時就需要擴(kuò)容。我們在 add( ) 方法中可以看到這樣一行代碼 :ensureCapacityInternal(size + 1);
此時 ensureCapacityInternal(11),minCapacity = 11。
調(diào)用 calculateCapacity(elementData, minCapacity) 后得到 minCapacity = 11
進(jìn)入到 ensureExplicitCapacity(),會發(fā)現(xiàn) minCapacity - elementData.length 是大于 0 的,所以會進(jìn)入到 grow() 方法里面,這里面就會進(jìn)行擴(kuò)容。
grow() 方法代碼
首先 oldCapacity = 10,newCapacity = oldCapacity + (oldCapacity >> 1),oldCapacity >> 1 相當(dāng)于 oldCapacity/2 也就是 10 / 2 =5,那么 newCapacity = 15。此時新的數(shù)組 就變成了可以容納15個元素的數(shù)據(jù),老的數(shù)組是 10個。最后使用 Arrays.copyOf() 工具方法來完成老數(shù)組到新數(shù)組的拷貝。看到這里你就會發(fā)現(xiàn)數(shù)組擴(kuò)容它是怎么來擴(kuò)的,是老數(shù)據(jù)的大小+ 老數(shù)組大小 >> 1(相當(dāng)于除以2) 來計算出來新數(shù)組的 大小,再使用 Arrays.copyOf() 來實現(xiàn)數(shù)據(jù)的拷貝。09?總結(jié)
缺點(diǎn)一、若我們要頻繁的向ArrayList 里面塞數(shù)據(jù),會導(dǎo)致它頻繁的數(shù)據(jù)擴(kuò)容,會導(dǎo)致系統(tǒng)性能下降,所以說不要頻繁的向ArrayList 插入數(shù)據(jù)。缺點(diǎn)二、ArrayList 基于數(shù)組來實現(xiàn)的,數(shù)組你要想往中間插入一個元素,那么你需把插入位置后面大量的元素都向后面挪動一位,性能較差。再說優(yōu)點(diǎn)、基于數(shù)組實現(xiàn),非常適合的是隨機(jī)讀取,你可以隨機(jī)讀取任意一個位置的元素,它是基于索引,下標(biāo)來直接定位元素在內(nèi)存中的位置的。我們在開發(fā)系統(tǒng)的時候還是會大量用到ArrayList 的,很多時候我們需要一個集合按照順序來放一些數(shù)據(jù),它最主要的功能就是它里面的元素是有順序的,會按照我們插入的順序來排列。本文是學(xué)習(xí)石杉老師JDK源碼課的一篇輸出總結(jié),結(jié)合老師的教案文檔看了一遍源碼,然后自己做了些補(bǔ)充,希望對你有用。
近期精彩內(nèi)容推薦:??

在看點(diǎn)這里
好文分享給更多人↓↓
評論
圖片
表情

