JAVA學(xué)習(xí)-ArrayList詳解

1.定義

ArrayList是實(shí)現(xiàn)了List接口的大小可變數(shù)組,實(shí)現(xiàn)了所有可選列表操作磕潮,運(yùn)行Null在內(nèi)的所有元素翠胰。以下源碼是基于JDK 1.7.0_79 (疑問(wèn)提出:1.如何實(shí)現(xiàn)大小可變的?)

2.結(jié)構(gòu)

ArrayList的類(lèi)的結(jié)構(gòu)如下圖所示

image

從上圖中可以清晰的ArrayList的體系結(jié)構(gòu),主要實(shí)現(xiàn)自脯、繼承的接口如下:

1.Collection 接口: Collection接口是所有集合類(lèi)的根節(jié)點(diǎn)之景,Collection表示一種規(guī)則,所有實(shí)現(xiàn)了Collection接口的類(lèi)遵循這種規(guī)則

2.List 接口: List是Collection的子接口膏潮,它是一個(gè)元素有序(按照插入的順序維護(hù)元素順序)锻狗、可重復(fù)、可以為null的集合

3.AbstractCollection 類(lèi): Collection接口的骨架實(shí)現(xiàn)類(lèi)戏罢,最小化實(shí)現(xiàn)了Collection接口所需要實(shí)現(xiàn)的工作量(疑問(wèn)屋谭,為啥要這么做)

4.AbstractList 類(lèi): List接口的骨架實(shí)現(xiàn)類(lèi),最小化實(shí)現(xiàn)了List接口所需要實(shí)現(xiàn)的工作量

5.Cloneable 接口: 實(shí)現(xiàn)了該接口的類(lèi)可以顯示的調(diào)用Object.clone()方法龟糕,合法的對(duì)該類(lèi)實(shí)例進(jìn)行字段復(fù)制桐磁,如果沒(méi)有實(shí)現(xiàn)Cloneable接口的實(shí)例上調(diào)用Obejct.clone()方法,會(huì)拋出CloneNotSupportException異常讲岁。正常情況下我擂,實(shí)現(xiàn)了Cloneable接口的類(lèi)會(huì)以公共方法重寫(xiě)Object.clone()

6.Serializable 接口: 實(shí)現(xiàn)了該接口標(biāo)示了類(lèi)可以被序列化和反序列化,具體的 查詢序列化詳解

7.RandomAccess 接口: 實(shí)現(xiàn)了該接口的類(lèi)支持快速隨機(jī)訪問(wèn)

3.實(shí)現(xiàn)原理

下面通過(guò)源碼來(lái)分析ArrayList的實(shí)現(xiàn)原理缓艳,主要的關(guān)注的內(nèi)容如下幾點(diǎn)

3.1 底層結(jié)構(gòu)
3.1.1 基礎(chǔ)屬性

ArrayList部分源碼如下:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final int DEFAULT_CAPACITY = 10;

    private static final Object[] EMPTY_ELEMENTDATA = {};

    private transient Object[] elementData;
    
    private int size;

    //...省略部分代碼
}

如上代碼中為ArrayList的主要屬性

  1. DEFAULT_CAPACITY:默認(rèn)容量校摩,即為初始值大小

  2. EMPTY_ELEMENTDATA:共享的空數(shù)組,用于初始化空實(shí)例

  3. elementData:ArrayList內(nèi)部結(jié)構(gòu)阶淘,是一個(gè)Object[]類(lèi)型的數(shù)組

  4. size:數(shù)組長(zhǎng)度大小

3.1.2 構(gòu)造方法

如下為ArrayList的構(gòu)造方法

1.public ArrayList(int initialCapacity)

2.public ArrayList()

3.public ArrayList(Collection<? extends E> c){
    elementData = c.toArray();
    size = elementData.length;
    // c.toArray might (incorrectly) not return Object[] (see 6260652)
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, size, Object[].class);
}

1.構(gòu)造方法1衙吩,表示接受指定地容量值,初始化創(chuàng)建數(shù)組溪窒,建議在可估算數(shù)組大小時(shí),創(chuàng)建ArrayList可指定

2.構(gòu)造方法2坤塞,是默認(rèn)的構(gòu)造方法冯勉,它將創(chuàng)建一個(gè)空數(shù)組

3.構(gòu)造方法3,接收一個(gè)Collection的實(shí)體摹芙,將該Collection實(shí)體轉(zhuǎn)換為ArrayList對(duì)象

3.1.3 主干流程

1.添加指定元素代碼如下

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

可以看到實(shí)際上只有3行代碼灼狰,其流程主要如下:

1.擴(kuò)容 (這里便解釋了,在介紹時(shí)提出的問(wèn)題):

主要源碼如下

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

//最大數(shù)組容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}
  • 第一個(gè)方法的邏輯為:判斷是不是第一次添加元素浮禾,若為第一次交胚,則設(shè)置初始化大小為默認(rèn)的值10,否則使用傳入的參數(shù)

  • 第二個(gè)方法的邏輯為:若長(zhǎng)度大于數(shù)組長(zhǎng)度,則擴(kuò)容

  • 第三個(gè)方法的邏輯為:

    1·擴(kuò)容的大小為3/2倍原數(shù)組長(zhǎng)度

    2.若值newCapacity比傳入值minCapacity還要小盈电,則使用傳入minCapacity蝴簇,若newCapacity比設(shè)定的最大數(shù)組容量大,則使用最大整數(shù)值

    3.實(shí)際擴(kuò)容挣轨,使用了Arrays.copyof(elementData, newCapacity)
    (此處有兩個(gè)問(wèn)題
    1.為啥擴(kuò)容是原來(lái)的3/2倍原數(shù)組的長(zhǎng)度?
    2.調(diào)用Arrays.copyOf(elementData, newCapacity)方法具體做了什么操作?
    )

2.賦值:將添加的值放置到size++的位置上

3.返回:返回true

2.添加指定元素到指定的位置上代碼如下

public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

其流程為

1.校驗(yàn)下標(biāo):調(diào)用rangeCheckForAdd方法進(jìn)行下標(biāo)校驗(yàn)军熏,不正確則會(huì)拋出IndexOutOfBoundsException異常

2.擴(kuò)容:詳見(jiàn)上部分中做的介紹

3.移動(dòng)數(shù)據(jù):將數(shù)據(jù)index后面的數(shù)據(jù),都向后移動(dòng)

4.賦值:將加入的值放置到index位置中

5.長(zhǎng)度增加:長(zhǎng)度增加

4.常用的方法

4.1 基本方法

ArrayList基本的方法如下所示

方法 說(shuō)明 實(shí)現(xiàn)原理
boolean contains(Object o) 返回列表中包含指定的元素 內(nèi)部調(diào)用indexOf()方法進(jìn)行判斷卷扮,遍歷列表進(jìn)行查詢荡澎,需要注意的是需要實(shí)現(xiàn)equals方法比較對(duì)象是否相等
E get(int index) 返回此列表中指定位置上的元素 內(nèi)部主要兩步,首先檢查索引晤锹,然后獲取數(shù)組對(duì)象索引的元素
E set(int index, E element) 用指定元素替代指定位置上的元素 需要注意的是該方法還會(huì)返回老的元素的值摩幔,內(nèi)部主要三步,首先檢查索引鞭铆,然后獲取老的元素或衡,賦值并返回
E remove(int index) 刪除指定位置上的元素 內(nèi)部實(shí)現(xiàn)步驟為 1.檢查索引 2.獲取老的元素 3.將index后的元素向前移動(dòng) 4.最后一個(gè)元素置為null并返回老的元素
boolean remove(Object o) 刪除列表中首次出現(xiàn)指定元素(如果存在) 內(nèi)部實(shí)現(xiàn)為 1.判斷為NULL,若是則遍歷并刪除 2.若不是則遍歷并刪除

4.2 ArrayList遍歷

ArrayList遍歷方式主要有以下幾種方式

1.使用for循環(huán)的方式车遂,代碼如下

for (int i = 0; i < arrayList.size(); i++) {
    System.out.println(arrayList.get(i));
}

2.使用foreach方式

for (String str : arrayList) {
    System.out.println(str);
}

3.使用Iterator迭代器方式

Iterator<String> ite = arrayList.listIterator();
while (ite.hasNext()){
    System.out.println(ite.next());
}

5.常見(jiàn)問(wèn)題

1.問(wèn)題描述

在使用ArrayList比較常見(jiàn)的一個(gè)問(wèn)題就是在遍歷ArrayList的時(shí)候調(diào)用remove()方法進(jìn)行元素的刪除操作,從而得到意想不到的結(jié)果封断,本人在開(kāi)發(fā)過(guò)程中也遇到過(guò)這樣的問(wèn)題,所以在這里提出了舶担,希望能夠幫助到大家坡疼。

2.實(shí)例及分析
如下代碼中,在遍歷List時(shí)衣陶,調(diào)用了remove方法柄瑰,刪除元素a

//arrayList中的值為 [a,a,c,a,a]
for (int i = 0; i < arrayList.size(); i++) {
    if (arrayList.get(i) == "a") {
        arrayList.remove(i);
    }
}
System.out.println(arrayList);

這段代碼看似解決了刪除列表中所有的a元素,但是刪除后得出List的結(jié)果為[a, c, a]剪况,為什么這種方式?jīng)]有達(dá)到想要的效果教沾,其實(shí)仔細(xì)分析后會(huì)發(fā)現(xiàn),在調(diào)用remove()方法時(shí)List的長(zhǎng)度會(huì)發(fā)生變化而且元素的位置會(huì)發(fā)生移動(dòng)译断,從而在遍歷時(shí)list實(shí)際上是變化的授翻,例如
當(dāng)i=0時(shí),此時(shí)list中的元素為[a,a,c,a,a],
但當(dāng)i=1時(shí),此時(shí)List中的元素為[a,c,a,a],元素的位置發(fā)生了移動(dòng)堪唐,從而導(dǎo)致在遍歷的過(guò)程中不能達(dá)到刪除的效果
3.解決方案
通過(guò)上述的分析可以看出隆箩,出現(xiàn)問(wèn)題的原因是元素的位置發(fā)生了移動(dòng),從而導(dǎo)致異常的結(jié)果

方案一羔杨、逆向遍歷List刪除,代碼如下,這種做法可行主要是因?yàn)閞emove()方法刪除index處的元素時(shí)杨蛋,是將index+1到size-1索引處的元素前移兜材,而逆向遍歷可以避免元素位置的移動(dòng)

for (int i = arrayList.size()-1; i >=0 ; i--) {
    if (arrayList.get(i) == "a") {
        arrayList.remove(i);
    }
}
System.out.println(arrayList);

方案二、使用迭代器中的remove方法逞力,迭代器具體參考Iterator詳解曙寡,主要代碼如下(這種方式比較推薦)

Iterator<String> ite = arrayList.listIterator();
while (ite.hasNext()){
    if(ite.next() == "a")
        ite.remove();
}
System.out.println(arrayList);

6.總結(jié)

本文主要講解ArrayList的底層實(shí)現(xiàn)、主要流程寇荧、常用方法等举庶,有寫(xiě)的不好的地方還望指正

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市揩抡,隨后出現(xiàn)的幾起案子户侥,更是在濱河造成了極大的恐慌,老刑警劉巖峦嗤,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蕊唐,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡烁设,警方通過(guò)查閱死者的電腦和手機(jī)替梨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)装黑,“玉大人副瀑,你說(shuō)我怎么就攤上這事×堤罚” “怎么了糠睡?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)箕别。 經(jīng)常有香客問(wèn)我铜幽,道長(zhǎng),這世上最難降的妖魔是什么串稀? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任除抛,我火速辦了婚禮,結(jié)果婚禮上母截,老公的妹妹穿的比我還像新娘到忽。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布喘漏。 她就那樣靜靜地躺著护蝶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪翩迈。 梳的紋絲不亂的頭發(fā)上持灰,一...
    開(kāi)封第一講書(shū)人閱讀 51,155評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音负饲,去河邊找鬼堤魁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛返十,可吹牛的內(nèi)容都是我干的妥泉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼洞坑,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼盲链!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起迟杂,我...
    開(kāi)封第一講書(shū)人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤刽沾,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后排拷,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體悠轩,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年攻泼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瘸羡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片先蒋。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出颊糜,到底是詐尸還是另有隱情肮街,我是刑警寧澤铭拧,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布惹恃,位于F島的核電站,受9級(jí)特大地震影響傍睹,放射性物質(zhì)發(fā)生泄漏隔盛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一拾稳、第九天 我趴在偏房一處隱蔽的房頂上張望吮炕。 院中可真熱鬧,春花似錦访得、人聲如沸龙亲。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鳄炉。三九已至杜耙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拂盯,已是汗流浹背佑女。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谈竿,地道東北人珊豹。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像榕订,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蜕便,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

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

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法劫恒,類(lèi)相關(guān)的語(yǔ)法,內(nèi)部類(lèi)的語(yǔ)法轿腺,繼承相關(guān)的語(yǔ)法两嘴,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,623評(píng)論 18 399
  • Java源碼研究之容器(1) 如何看源碼 很多時(shí)候我們看源碼, 看完了以后經(jīng)常也沒(méi)啥收獲, 有些地方看得懂, 有些...
    駱駝騎士閱讀 993評(píng)論 0 22
  • java筆記第一天 == 和 equals ==比較的比較的是兩個(gè)變量的值是否相等族壳,對(duì)于引用型變量表示的是兩個(gè)變量...
    jmychou閱讀 1,497評(píng)論 0 3
  • Collection ├List │├LinkedList │├ArrayList │└Vector │└Stac...
    AndyZX閱讀 874評(píng)論 0 1
  • 最近在看的是BBC紀(jì)錄片-地球脈動(dòng)特別篇之奇跡世界,主要介紹的大自然里的動(dòng)物拢操,跟央媽播的(動(dòng)物世界)的區(qū)別在于锦亦,這...
    柶夕木木閱讀 6,097評(píng)論 0 2