ArrayList 源碼分析(JDK1.8)

一、目錄

  • 源碼分析
    1. 構(gòu)造器
    2. 插入
    3. 刪除
    4. 迭代器(注意: foreach 做刪除操作拋出異常問題)
  • 總結(jié)
  • 示例代碼
  • 參考資料

二洲愤、源碼分析

1. 構(gòu)造器

private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;

// 參數(shù)表示初始化容量
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        // 空數(shù)組 {}
        // private static final Object[] EMPTY_ELEMENTDATA = {};
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        //容量 < 0 , 報異常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

// 無參構(gòu)造器
public ArrayList() {
    // 默認(rèn)創(chuàng)建一個空數(shù)組 {}
    // private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

兩個構(gòu)造器都是初始化底層數(shù)組 elementData , 區(qū)別在于無參構(gòu)造方法會創(chuàng)建一個空數(shù)組, 插入元素時, 擴(kuò)容將會按默認(rèn)值重新初始化數(shù)組, 有參構(gòu)造方法則會根據(jù)參數(shù)創(chuàng)建一個長度 >=0 的數(shù)組.


2. 插入

private int size;
protected transient int modCount = 0;
private static final int DEFAULT_CAPACITY = 10;

public boolean add(E e) {
    // 確定集合容量(是否擴(kuò)容)
    ensureCapacityInternal(size + 1); 
    // 添加元素到集合的尾部, size +1
    elementData[size++] = e;
    return true;
}

public void add(int index, E element) {
    // 檢查插入索引是否符合規(guī)范
    rangeCheckForAdd(index);
    // 確定集合容量(是否擴(kuò)容)
    ensureCapacityInternal(size + 1);
    // 將 index 及其之后的元素向后移動一位
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 替換掉 index 元素
    elementData[index] = element;
    size++;
}

public boolean addAll(Collection<? extends E> c) {
    // 集合轉(zhuǎn)數(shù)組
    Object[] a = c.toArray();
    int numNew = a.length
    // 確定集合容量(是否擴(kuò)容)
    ensureCapacityInternal(size + numNew);  
    // 將數(shù)組元素添加到集合尾部
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

對于在數(shù)組尾部插入元素

  1. 檢查是否需要擴(kuò)容
  2. 將元素插入至數(shù)組尾部

對于根據(jù)索引插入元素

  1. 檢查是否需要擴(kuò)容
  2. 將 index 及其之后的元素向后移動一位
  3. 替換掉 index 元素

對于插入集合元素

  1. 將集合轉(zhuǎn)成數(shù)組
  2. 檢查是否需要擴(kuò)容
  3. 將數(shù)組元素添加到集合尾部
private void ensureCapacityInternal(int minCapacity) {
    // 假如集合初始化時為空數(shù)組 {}
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 獲取最小容量
        // private static final int DEFAULT_CAPACITY = 10;
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    // 確定集合容量(是否擴(kuò)容)
    ensureExplicitCapacity(minCapacity);
}

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

    // 假如集合容量大于初始化長度
    if (minCapacity - elementData.length > 0)
        // 擴(kuò)容
        grow(minCapacity);
}
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    // 等價于 int newCapacity = oldCapacity + (oldCapacity * 0.5);
    // 擴(kuò)容1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 擴(kuò)容
    elementData = Arrays.copyOf(elementData, newCapacity);
}

擴(kuò)容是按照之前容量的1.5倍進(jìn)行擴(kuò)充, 假如初始化數(shù)組為空, 則擴(kuò)容容量為10, 擴(kuò)容容量最大值為 Integer.MAX_VALUE


3. 刪除

public E remove(int index) {
    // 檢查索引是否符合規(guī)范, index >= size 拋出異常
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);
    // 從索引開始到最后的數(shù)組長度
    int numMoved = size - index - 1;
    if (numMoved > 0)
        // 將 index + 1 及之后的元素向前移動一位雹有,覆蓋被刪除值
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 最后一個元素設(shè)置為 null, size -1
    elementData[--size] = null; 

    return oldValue;
}


public boolean remove(Object o) {
    if (o == null) {
        // 遍歷整個數(shù)組
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}


private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; 
}


public void clear() {
    modCount++;

    // 遍歷, 清空所有元素
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
}

根據(jù)索引刪除元素

  1. 將 index + 1 及之后的元素向前移動一位阴挣,覆蓋被刪除值
  2. 最后一個元素設(shè)置為 null, size -1
  3. 返回被刪除的元素

根據(jù)對象刪除元素

  1. 遍歷整個數(shù)組
  2. 判斷數(shù)組是否含有該元素
  3. 刪除該元素

清空元素

  1. 遍歷, 將所有元素設(shè)置為null

注意:

public void trimToSize() {
    modCount++;
    if (size < elementData.length) {
        elementData = (size == 0)
          ? EMPTY_ELEMENTDATA
          : Arrays.copyOf(elementData, size);
    }
}

往 ArrayList 插入大量元素, 然后又刪除了很多元素, 此時底層數(shù)組會空出大量的空間. 使用 trimToSize() 方法可以解決這個問題


4. 迭代器

private class Itr implements Iterator<E> {
    // 下一個元素索引
    int cursor;       
    // 上一個元素索引
    int lastRet = -1; 
    int expectedModCount = modCount;

    public boolean hasNext() {
        // 如果cursor == size徘层,說明已經(jīng)遍歷完了呵俏,上一次遍歷的是最后一個元素
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        // 檢驗在遍歷過程中集合是否有做 add, remove 操作
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        // 下一個元素索引 = 元素索引 +1
        cursor = i + 1;
        // 返回迭代對象, 上一個元素索引 = 元素索引
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        // 假如 lastRet < 0, 未獲取元素, 無法做刪除操作, 拋出異常
        if (lastRet < 0)
            throw new IllegalStateException();
        // 檢驗在遍歷過程中集合是否有做 add, remove 操作
        checkForComodification();

        try {
            // 根據(jù)索引做刪除操作
            ArrayList.this.remove(lastRet);
            // 下一個元素索引 = 元素索引
            cursor = lastRet;
            // 下一個元素索引 = -1
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
    
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

注意:
關(guān)于 foreach 遍歷做刪除操作時拋出異常的問題

  ArrayList<Object> list = new ArrayList<>();
  list.add(1);
  list.add(2);
  list.add(3);
  list.add(4);
  
  for (Object o : list) {
      if (Objects.equals(2, o)) {
          list.remove(o);
      }
  }

Java 中的 foreach 經(jīng)過編譯器編譯之后會被轉(zhuǎn)換成迭代器遍歷的方式, 等價于

  ArrayList<Object> list = new ArrayList();
  list.add(1);
  list.add(2);
  list.add(3);
  list.add(4);
  Iterator var2 = list.iterator();
  
  while(var2.hasNext()) {
      Object o = var2.next();
      if (Objects.equals(2, o)) {
          list.remove(o);
      }
  }

根據(jù)上面的代碼可知, list 經(jīng)過 remove 操作 modCount 會進(jìn)行 +1 操作, 等到下一輪循環(huán)的 next() 方法中, checkForComodification() 方法用來判斷 list 是否有進(jìn)行 add / remove 操作, 此時 modCount +1, modCount != expectedModCount 為 true , 拋出 ConcurrentModificationException 異常


三铆惑、總結(jié)

  • ArrayList 基于數(shù)組實現(xiàn), 可擴(kuò)容
  • 添加元素時可能要擴(kuò)容, 刪除元素時不會自動減少容量, 可以使用 trimToSize() 方法進(jìn)行縮容處理**
  • 線程不安全
  • add(int index, E element) 添加元素到指定位置, 需要將索引及其以后的元素整塊向后移動一位(使用 System.arraycopy 方法)
  • remove(Object o) 需要遍歷數(shù)組
  • remove(int index) 不需要遍歷, 效率高于remove(Object o)
  • contains(E) 亦需要遍歷數(shù)組

四范嘱、示例代碼

public static void main(String[] args) {
    byte[]  srcBytes = new byte[]{1,2,3,4,5,6};  // 源數(shù)組
    System.arraycopy(srcBytes, 1, srcBytes, 3, 2);
    System.out.println(Arrays.toString(srcBytes));
    byte[]  srcBytes2 = new byte[]{1,2,3,4,5,6};
    byte[]  desBytes = Arrays.copyOf(srcBytes2, 3);
    System.out.println(Arrays.toString(desBytes));

    ArrayList<Object> list = new ArrayList<>();
    list.add("sss");
    list.add("xxx");
    list.add("zzz");
    list.add("yyy");
    list.add(1, "ooo");
    list.remove(1);
    list.remove("yyy");
    list.set(1, "mmm");
    list.get(1);
    list.trimToSize();
    System.out.println(list.toString());
    Iterator<Object> iterator = list.iterator();
    while (iterator.hasNext()) {
        Object temp = iterator.next();
        System.out.println(temp);
        if (Objects.equals("zzz", temp)) {
            iterator.remove();
        }
    }
    System.out.println(list.toString());

    ArrayList<Object> list2 = new ArrayList<>(2);
    list2.add(1);
    list2.add(2);
    list.addAll(list2);

    System.out.println("---");

    for (Object o : list) {
        if (Objects.equals("sss", o)) {
            list.remove(o);
        }
    }
    System.out.println(list.toString());
}

運(yùn)行結(jié)果:

[1, 2, 3, 2, 3, 6]
[1, 2, 3]
[sss, mmm, zzz]
sss
mmm
zzz
[sss, mmm]
---
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at Main.main(Main.java:145)

五、參考資料

Java集合之ArrayList源碼分析
ArrayList源碼解析
ArrayList 源碼分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末员魏,一起剝皮案震驚了整個濱河市丑蛤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌撕阎,老刑警劉巖受裹,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異虏束,居然都是意外死亡棉饶,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進(jìn)店門镇匀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來照藻,“玉大人,你說我怎么就攤上這事汗侵⌒衣疲” “怎么了?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵晰韵,是天一觀的道長发乔。 經(jīng)常有香客問我,道長雪猪,這世上最難降的妖魔是什么栏尚? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮浪蹂,結(jié)果婚禮上抵栈,老公的妹妹穿的比我還像新娘。我一直安慰自己坤次,他們只是感情好古劲,可當(dāng)我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著缰猴,像睡著了一般产艾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天闷堡,我揣著相機(jī)與錄音隘膘,去河邊找鬼。 笑死杠览,一個胖子當(dāng)著我的面吹牛弯菊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播踱阿,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼管钳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了软舌?” 一聲冷哼從身側(cè)響起才漆,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎佛点,沒想到半個月后醇滥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡超营,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年鸳玩,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片糟描。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡怀喉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出船响,到底是詐尸還是另有隱情躬拢,我是刑警寧澤,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布见间,位于F島的核電站聊闯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏米诉。R本人自食惡果不足惜菱蔬,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望史侣。 院中可真熱鬧拴泌,春花似錦、人聲如沸惊橱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽税朴。三九已至回季,卻和暖如春家制,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背泡一。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工颤殴, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鼻忠。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓涵但,卻偏偏與公主長得像,于是被迫代替她去往敵國和親粥烁。 傳聞我的和親對象是個殘疾皇子贤笆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,585評論 2 359

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

  • ArrayList感覺是在做項目中遇到最多的,先從添加元素開始講起讨阻,請跟隨我的思路1.add(E e),最常用的篡殷,...
    何甜甜在嗎閱讀 338評論 0 0
  • ArrayList的成員變量分析钝吮。 ArrayList的常見創(chuàng)建方式。2.1 空構(gòu)造函數(shù) 2.2 初始化集合大小...
    阿貍404閱讀 214評論 0 0
  • ArrayList是最常用的數(shù)組板辽,這里我們分析一下源碼: ArrayList繼承了AbstractList<E> ...
    說dian什么好呢閱讀 761評論 0 0
  • 一奇瘦、ArrayList的數(shù)據(jù)結(jié)構(gòu):ArrayList的數(shù)據(jù)結(jié)構(gòu)如下: 說明:通過查看源碼可以知道ArrayList...
    Vechace閱讀 619評論 0 0
  • ArrayList ArrayList就是傳說中的動態(tài)數(shù)組,就是Array的復(fù)雜版本劲弦,它提供了如下一些好處:動態(tài)的...
    史路比閱讀 227評論 0 0