ArrayList底層原理

ArrayList

ArrayList是最常見以及每個Java開發(fā)者最熟悉的集合類了,顧名思義珍特,ArrayList就是一個以數(shù)組形式實現(xiàn)的集合,以一張表格來看一下ArrayList里面有哪些基本的元素:

四個關(guān)注點在ArrayList上的答案

以后每篇文章在講解代碼前魔吐,都會先對于一個集合關(guān)注的四個點以表格形式做一個解答:

ArrayList屬性

public?class?ArrayList?extends?AbstractList?implements?List,?RandomAccess,?Cloneable,?Serializable?{??

//?序列化id??

private?static?final?long?serialVersionUID?=?8683452581122892189L;??

//?默認初始的容量??

private?static?final?int?DEFAULT_CAPACITY?=?10;??

//?一個空對象??

private?static?final?Object[]?EMPTY_ELEMENTDATA?=?new?Object[0];??

//?一個空對象次坡,如果使用默認構(gòu)造函數(shù)創(chuàng)建,則默認對象內(nèi)容默認是該值??

private?static?final?Object[]?DEFAULTCAPACITY_EMPTY_ELEMENTDATA?=?new?Object[0];??

//?當前數(shù)據(jù)對象存放地方画畅,當前對象不參與序列化??

transient?Object[]?elementData;??

//?當前數(shù)組長度??

private?int?size;??

//?數(shù)組最大長度??

private?static?final?int?MAX_ARRAY_SIZE?=?2147483639;??


//?省略方法砸琅。。??

}??

ArrayList構(gòu)造函數(shù)

無參構(gòu)造函數(shù)

如果不傳入?yún)?shù)轴踱,則使用默認無參構(gòu)建方法創(chuàng)建ArrayList對象症脂,如下:


public?ArrayList()?{??

this.elementData?=?DEFAULTCAPACITY_EMPTY_ELEMENTDATA;??

}??

注意:此時我們創(chuàng)建的ArrayList對象中的elementData中的長度是1,size是0,當進行第一次add的時候,elementData將會變成默認的長度:10.

帶int類型的構(gòu)造函數(shù)

如果傳入?yún)?shù)诱篷,則代表指定ArrayList的初始數(shù)組長度壶唤,傳入?yún)?shù)如果是大于等于0,則使用用戶的參數(shù)初始化棕所,如果用戶傳入的參數(shù)小于0闸盔,則拋出異常,構(gòu)造方法如下:

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);??

????}??

}??

添加元素

有這么一段代碼:

publicstaticvoidmain(String[] args)

{

????List list = newArrayList();

????list.add("000");

????list.add("111");

}

看下底層會做什么琳省,進入add方法的源碼來看一下:


publicbooleanadd(E e) {

?????ensureCapacity(size + 1);? // Increments modCount!!

?????elementData[size++] = e;

?????returntrue;

}

先不去管第2行的ensureCapacity方法迎吵,這個方法是擴容用的,底層實際上在調(diào)用add方法的時候只是給elementData的某個位置添加了一個數(shù)據(jù)而已针贬,用一張圖表示的話是這樣的:

多說一句击费,我這么畫圖有一定的誤導性桦他。elementData中存儲的應該是堆內(nèi)存中元素的引用,而不是實際的元素圆仔,這么畫給人一種感覺就是說elementData數(shù)組里面存放的就是實際的元素,這是不太嚴謹?shù)哪枇印2贿^這么畫主要是為了方便起見坪郭,只要知道這個問題就好了。

擴容

我們看一下信姓,構(gòu)造ArrayList的時候鸵隧,默認的底層數(shù)組大小是10:


publicArrayList() {

????this(10);

}

那么有一個問題來了,底層數(shù)組的大小不夠了怎么辦意推?答案就是擴容,這也就是為什么一直說ArrayList的底層是基于動態(tài)數(shù)組實現(xiàn)的原因菊值,動態(tài)數(shù)組的意思就是指底層的數(shù)組大小并不是固定的,而是根據(jù)添加的元素大小進行一個判斷昵宇,不夠的話就動態(tài)擴容儿子,擴容的代碼就在ensureCapacity里面:

publicvoidensureCapacity(intminCapacity) {

modCount++;

intoldCapacity = elementData.length;

if(minCapacity > oldCapacity) {

????Object oldData[] = elementData;

????intnewCapacity = (oldCapacity * 3)/2+ 1;

????????if(newCapacity < minCapacity)

????newCapacity = minCapacity;

???????????// minCapacity is usually close to size, so this is a win:

???????????elementData = Arrays.copyOf(elementData, newCapacity);

}

}

看到擴容的時候把元素組大小先乘以3,再除以2蒋譬,最后加1》钢可能有些人要問為什么?我們可以想:

1惠爽、如果一次性擴容擴得太大雷恃,必然造成內(nèi)存空間的浪費

2、如果一次性擴容擴得不夠旬痹,那么下一次擴容的操作必然比較快地會到來两残,這會降低程序運行效率把跨,要知道擴容還是比價耗費性能的一個操作

所以擴容擴多少,是JDK開發(fā)人員在時間崔赌、空間上做的一個權(quán)衡耸别,提供出來的一個比較合理的數(shù)值。最后調(diào)用到的是Arrays的copyOf方法秀姐,將元素組里面的內(nèi)容復制到新的數(shù)組里面去:


publicstatic T[] copyOf(U[] original, intnewLength, Class newType) {

???????T[] copy = ((Object)newType == (Object)Object[].class)

???????????? (T[]) newObject[newLength]

???????????: (T[]) Array.newInstance(newType.getComponentType(), newLength);

???????System.arraycopy(original, 0, copy, 0,

????????????????????????Math.min(original.length, newLength));

???????returncopy;

}

用一張圖來表示就是這樣的:

刪除元素

接著我們看一下刪除的操作省有。ArrayList支持兩種刪除方式:

1、按照下標刪除

2蠢沿、按照元素刪除,這會刪除ArrayList中與指定要刪除的元素匹配的第一個元素

對于ArrayList來說熊锭,這兩種刪除的方法差不多,都是調(diào)用的下面一段代碼:


intnumMoved = size - index - 1;

if(numMoved > 0)

????System.arraycopy(elementData, index+1, elementData, index,

?????????????numMoved);

elementData[--size] = null; // Let gc do its work

其實做的事情就是兩件:

1碗殷、把指定元素后面位置的所有元素锌妻,利用System.arraycopy方法整體向前移動一個位置

2、最后一個位置的元素指定為null仿粹,這樣讓gc可以去回收它

比方說有這么一段代碼:


publicstaticvoidmain(String[] args)

{

????List list = newArrayList();

????list.add("111");

????list.add("222");

????list.add("333");

????list.add("444");

????list.add("555");

????list.add("666");

????list.add("777");

????list.add("888");

????list.remove("333");

}

用圖表示是這樣的:

插入元素

看一下ArrayList的插入操作吭历,插入操作調(diào)用的也是add方法,比如:

`publicstaticvoidmain(String[] args)

{

????List list = newArrayList();

????list.add("111");

????list.add("222");

????list.add("333");

????list.add("444");

????list.add("555");

????list.add("666");

????list.add("777");

????list.add("888");

????list.add(2, "000");

????System.out.println(list);

}`

有一個地方不要搞錯了摩骨,第12行的add方法的意思是朗若,往第幾個元素后面插入一個元素,像第12行就是往第二個元素后面插入一個000灾馒∏沧埽看一下運行結(jié)果也證明了這一點:

1[111, 222, 000, 333, 444, 555, 666, 777, 888]

還是看一下插入的時候做了什么:

publicvoidadd(intindex, E element) {

if(index > size || index < 0)

????thrownewIndexOutOfBoundsException(

????"Index: "+index+", Size: "+size);

????ensureCapacity(size+1);? // Increments modCount!!

System.arraycopy(elementData, index, elementData, index + 1,

?????????size - index);

elementData[index] = element;

size++;

}

看到插入的時候旭斥,按照指定位置,把從指定位置開始的所有元素利用System,arraycopy方法做一個整體的復制琉预,向后移動一個位置(當然先要用ensureCapacity方法進行判斷蒿褂,加了一個元素之后數(shù)組會不會不夠大)啄栓,然后指定位置的元素設(shè)置為需要插入的元素,完成了一次插入的操作昙楚。用圖表示這個過程是這樣的:

ArrayList的優(yōu)缺點

從上面的幾個過程總結(jié)一下ArrayList的優(yōu)缺點。ArrayList的優(yōu)點如下:

1削葱、ArrayList底層以數(shù)組實現(xiàn)析砸,是一種隨機訪問模式,再加上它實現(xiàn)了RandomAccess接口首繁,因此查找也就是get的時候非诚掖快

2、ArrayList在順序添加一個元素的時候非常方便胁塞,只是往數(shù)組里面添加了一個元素而已

不過ArrayList的缺點也十分明顯:

1、刪除元素的時候状土,涉及到一次元素復制伺糠,如果要復制的元素很多,那么就會比較耗費性能

2累驮、插入元素的時候舵揭,涉及到一次元素復制,如果要復制的元素很多置侍,那么就會比較耗費性能

因此拦焚,ArrayList比較適合順序添加、隨機訪問的場景秕衙。

ArrayList和Vector的區(qū)別

ArrayList是線程非安全的僵刮,這很明顯鹦牛,因為ArrayList中所有的方法都不是同步的勇吊,在并發(fā)下一定會出現(xiàn)線程安全問題汉规。那么我們想要使用ArrayList并且讓它線程安全怎么辦?一個方法是用Collections.synchronizedList方法把你的ArrayList變成一個線程安全的List鲫忍,比如:

List synchronized List = Collections.synchronizedList(list);

synchronized List.add("aaa");

synchronized List.add("bbb");

for(inti = 0; i < synchronizedList.size(); i++)

{

????System.out.println(synchronizedList.get(i));

}

另一個方法就是Vector悟民,它是ArrayList的線程安全版本,其實現(xiàn)90%和ArrayList都完全一樣射亏,區(qū)別在于:

1智润、Vector是線程安全的,ArrayList是線程非安全的

2窟绷、Vector可以指定增長因子兼蜈,如果該增長因子指定了,那么擴容的時候會每次新的數(shù)組大小會在原數(shù)組的大小基礎(chǔ)上加上增長因子为狸;如果不指定增長因子辐棒,那么就給原數(shù)組大小*2,源代碼是這樣的:

intnewCapacity = oldCapacity + ((capacityIncrement > 0) ?

?????????????????????????????????capacityIncrement : oldCapacity);

為什么ArrayList的elementData是用transient修飾的漾根?

最后一個問題立叛,我們看一下ArrayList中的數(shù)組贡茅,是這么定義的:

1privatetransientObject[] elementData;

不知道大家有沒有想過其做,為什么elementData是使用transient修飾的呢赁还?關(guān)于這個問題,說說我的看法蹈胡。我們看一下ArrayList的定義:


publicclassArrayList extendsAbstractList

????????implementsList, RandomAccess, Cloneable, java.io.Serializable

看到ArrayList實現(xiàn)了Serializable接口朋蔫,這意味著ArrayList是可以被序列化的,用transient修飾elementData意味著我不希望elementData數(shù)組被序列化荷并。這是為什么青扔?因為序列化ArrayList的時候微猖,ArrayList里面的elementData未必是滿的,比方說elementData有10的大小凛剥,但是我只用了其中的3個,那么是否有必要序列化整個elementData呢傅瞻?顯然沒有這個必要盲憎,因此ArrayList中重寫了writeObject方法:


private void writeObject(java.io.ObjectOutputStream s)

????????throwsjava.io.IOException{

// Write out element count, and any hidden stuff

intexpectedModCount = modCount;

s.defaultWriteObject();

????????// Write out array length

???????s.writeInt(elementData.length);

????// Write out all elements in the proper order.

for(inti=0; i

???????????s.writeObject(elementData[i]);

????if(modCount != expectedModCount) {

???????????thrownewConcurrentModificationException();

????}

}

每次序列化的時候調(diào)用這個方法饼疙,先調(diào)用defaultWriteObject()方法序列化ArrayList中的非transient元素,elementData不去序列化它屏积,然后遍歷elementData磅甩,只序列化那些有的元素,這樣:

1渣聚、加快了序列化的速度

2、減小了序列化之后的文件大小

不失為一種聰明的做法奕枝,如果以后開發(fā)過程中有遇到這種情況隘道,也是值得學習、借鑒的一種思路谭梗。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末激捏,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子壹置,更是在濱河造成了極大的恐慌表谊,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件难咕,死亡現(xiàn)場離奇詭異余佃,居然都是意外死亡跨算,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門步势,熙熙樓的掌柜王于貴愁眉苦臉地迎上來背犯,“玉大人,你說我怎么就攤上這事倔矾。” “怎么了丰包?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵提陶,是天一觀的道長隙笆。 經(jīng)常有香客問我升筏,道長,這世上最難降的妖魔是什么铅忿? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任灵汪,我火速辦了婚禮,結(jié)果婚禮上峻凫,老公的妹妹穿的比我還像新娘览露。我一直安慰自己,他們只是感情好命锄,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布脐恩。 她就那樣靜靜地躺著侦讨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪只怎。 梳的紋絲不亂的頭發(fā)上怜俐,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天,我揣著相機與錄音贴谎,去河邊找鬼。 笑死澈魄,一個胖子當著我的面吹牛仲翎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鲫构,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼玫坛,長吁一口氣:“原來是場噩夢啊……” “哼湿镀!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起勉痴,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤蚀腿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后廓脆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體磁玉,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡蚊伞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了颅停。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掠拳。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖喊熟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情烦味,我是刑警寧澤壁拉,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布扇商,位于F島的核電站宿礁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏控汉。R本人自食惡果不足惜返吻,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望街佑。 院中可真熱鬧捍靠,春花似錦、人聲如沸磁携。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至疑俭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鬼贱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工舟误, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留姻乓,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓赖草,卻偏偏與公主長得像剪个,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子扣囊,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容