【Java集合】源碼分析之ArrayList

概述

ArrayList使我們從學(xué)習(xí)Java開始經(jīng)常使用到的一種集合類,也是我們在面試中經(jīng)常被面試官提起的一個知識點扔罪,仔細閱讀ArrayList的源碼,深入理解ArrayList的底層設(shè)計带膀,擴容機制撩银,增刪改查的詳細過程鹃共,有利于我們以后在開發(fā)過程中更加明確合適該使用ArrayList鬼佣,使用ArrayList有哪些好處。
我們先看下ArrayList的Diagram霜浴;

;

從上圖可以看到晶衷,ArrayList繼承了AbstractList,實現(xiàn)了Cloneable、Serializable晌纫、RandomAccess税迷、List<E>接口。從ArrayList的繼承關(guān)系可以發(fā)現(xiàn):

  1. 其中AbstractList和List<E>是規(guī)定了ArrayList作為一個集合框架所必須要具備的一些屬性和方法锹漱,這里面就包括了我們后面要分析的增刪改查一類箭养。
  2. 其次ArrayList實現(xiàn)RandomAccess接口表示它支持隨機快速訪問,通過get(index)查找ArrayList中的元素時間復(fù)雜度是O(1)哥牍,有些類則不具備這一特性毕泌,例如LinkedList;
  3. ArrayList實現(xiàn)Cloneable接口說明它支持被克隆/復(fù)制,其內(nèi)部提供了clone()方法供使用者調(diào)用來對ArrayList進行賦值嗅辣,但是其實現(xiàn)僅僅通過Arrays.copyOf完成了對ArrayList的淺復(fù)制撼泛,即當(dāng)arrayList中保存的是引用類型變量時,改變原ArrayList的數(shù)據(jù)澡谭,克隆的ArrayList隨之改變愿题。
  4. ArrayList實現(xiàn)Serializable接口說明它支持序列化,可以被序列化蛙奖。

ArrayList的重要屬性

我們先來看下ArrayList中的幾個重要屬性:

    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;

從源碼的英文注釋中潘酗,我們也可以大概了解到每個全局變量的用途:

  • DEFAULT_CAPACITY 表示ArrayList底層數(shù)組的默認(rèn)長度為10;
  • EMPTY_ELEMENTDATA 一個共享的空數(shù)組雁仲,當(dāng)使用ArrayList(0)或者ArrayList(Collection<? extends E> c ) c.size() = 0 時仔夺,將elementData指向該數(shù)組;
  • DEFAULTCAPACITY_EMPTY_ELEMENTDATA 也是一個空的數(shù)組伯顶,在第一次add元素的時候囚灼,用它來判斷數(shù)組大小是否設(shè)置為DEFAULT_CAPACITY骆膝;
  • elementData 真正裝在集合元素的底層數(shù)組
  • size 表示ArrayList中元素的個數(shù)

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

ArrayList中有三個構(gòu)造函數(shù):ArrayList() 祭衩、ArrayList(int initialCapacity)、ArrayList(Collection<? extends E> c)阅签。下面我們一次來看下這三個構(gòu)造函數(shù)的源碼

ArrayList()

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

這是我們使用最頻繁的無參構(gòu)造函數(shù)掐暮,最開始只是將elementData指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA這個空數(shù)組,也就是說當(dāng)我們調(diào)用無參構(gòu)造函數(shù)創(chuàng)建ArrayList政钟,但還沒有插入元素的時候路克,其底層是一個空數(shù)組,長度為0养交。在后面我們看add方法的時候會發(fā)現(xiàn)精算,第一次插入元素的時候,這個空數(shù)組會擴容到10碎连。

ArrayList(int initialCapacity)

public ArrayList(int initialCapacity) {
    //如果initialCapacity>0,則創(chuàng)建長度為initialCapacity的數(shù)組灰羽,將elementData指向該數(shù)組
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        //如果initialCapacity == 0 ,則將elementData指向EMPTY_ELEMENTDATA這個空數(shù)組
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        //initialCapacity<0,則直接拋出非法異常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

該方法的具體邏輯可以看如上的代碼注釋,如果初始化長度大于0廉嚼,則會創(chuàng)建一個長度為initialCapacity的數(shù)組玫镐,并將elementData指向該數(shù)組。如果我們預(yù)先知道需要創(chuàng)建的ArrayList長度怠噪,那么推薦使用這種方式來創(chuàng)建恐似,這樣可以避免不必要的擴容,減少系統(tǒng)開銷傍念。

ArrayList(Collection<? extends E> c)

public ArrayList(Collection<? extends E> c) {
    //將集合轉(zhuǎn)換為數(shù)組矫夷,并將elementData指向該數(shù)組
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        // c.toArray 可能(錯誤地)不返回 Object[]類型的數(shù)組,如果出現(xiàn)這種情況憋槐,則用Arrays.copyOf克隆一個新的 
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

這里的代碼其實也比較簡單口四,只是將傳入的集合轉(zhuǎn)為數(shù)組,將elementData指向該數(shù)組秦陋,其中涉及到JDK的一個Bug 6260652蔓彩。是指c.toArray返回的可能不是Object[]類型的數(shù)組,如果出現(xiàn)這種情況的話驳概,這里會用Arrays.copyOf重新創(chuàng)建一個等長度的Object數(shù)組赤嚼,并將原集合的元素插入到這個數(shù)組中,具體細節(jié)不在此深究顺又。

添加元素 & 擴容機制

add(E e) 與擴容機制

ArrayList是如何添加元素的更卒,在什么時候擴容呢?又是怎么擴容的呢稚照?這部分是我們在面試中經(jīng)常被問到的蹂空,下面我們來閱讀源碼進行解讀。

public boolean add(E e) {
    //檢查當(dāng)前底層數(shù)組容量果录,如果容量不夠則進行擴容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //將數(shù)組添加一個元素上枕,size加1
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    //如果elementData是空數(shù)組,那么取默認(rèn)長度10與minCapacity中的較大值弱恒,重新賦值給minCapacity
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

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

    // overflow-conscious code
    // 如果最小長度大于elementData的長度辨萍,那么需要擴容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

下面我們來看下擴容方法grow(minCapacity)的源碼;

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //擴容為原長度的1.5倍(原數(shù)組長度+ 原數(shù)組長度左移1位)
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果擴容1.5倍返弹,還是沒有達到最小數(shù)組長度锈玉,那么直接擴容到最小數(shù)組長度
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //如果新數(shù)組長度大于最大數(shù)組長度,就需要進一步比較 minCapacity 和 MAX_ARRAY_SIZE 的大小
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        //使用 Arrays.copyOf 構(gòu)建一個長度為 newCapacity 新數(shù)組 并將 elementData 指向新數(shù)組
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        //如果minCapacity小于0义起,則報內(nèi)存溢出
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        //比較 minCapacity 與 Integer.MAX_VALUE - 8 的大小如果大則放棄-8的設(shè)定拉背,設(shè)置為 Integer.MAX_VALUE
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

具體邏輯可以看代碼中的注釋,從上面的代碼中可以總結(jié)出下面比較重要的兩點:

  1. 每次擴容默终,會先擴容為原數(shù)組長度的1.5倍椅棺,如果擴容后的長度還是小于所需的最小容量抡诞,那么直接擴容到所需最小容量。
  2. 擴容的過程土陪,其實是將原數(shù)組中的元素拷貝到新數(shù)組中昼汗。所以ArrayList的擴容相對來說比較消耗性能。

add(int index, E element)

需要在指定索引位置添加元素時鬼雀,調(diào)用add(int index, E element)方法顷窒,我們來看下源碼:

    public void add(int index, E element) {
        //檢查索引位置是否越界,如果越界則拋出數(shù)組越界異常
        rangeCheckForAdd(index);
        //判斷是否需要擴容源哩,如果所需最小容量大于當(dāng)前數(shù)組長度鞋吉,則擴容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //調(diào)用 native 方法新型數(shù)組拷貝
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //在索引位置添加元素
        elementData[index] = element;
        size++;
    }

    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

一個數(shù)組是不能在角標(biāo)位置直接插入元素的,ArrayList 通過數(shù)組拷貝的方法將指定角標(biāo)位置以及其后續(xù)元素整體向后移動一個位置励烦,空出 index 角標(biāo)的位置谓着,來賦值新的元素。System.arraycopy方法的作用是:
將一個數(shù)組src起始srcPos角標(biāo)后length長度間的元素坛掠,賦值到dest數(shù)組中destPos到destPos+length-1長度角標(biāo)位置上赊锚。只是這里調(diào)用的時候src和dest都是同一個數(shù)組elementData,得到的效果就是從索引位置開始的所有元素后移一位。

addAll(Collection<? extends E> c)

批量添加元素屉栓,調(diào)用方法addAll(Collection<? extends E> c)舷蒲,這里邏輯與add(E e)方法相差不大:

    public boolean addAll(Collection<? extends E> c) {
        // 調(diào)用 c.toArray 將集合轉(zhuǎn)化數(shù)組
        Object[] a = c.toArray();
        int numNew = a.length;
        //擴容檢查以及擴容
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //將參數(shù)集合中的元素添加到原來數(shù)組 [size,size + numNew -1] 的角標(biāo)位置上友多。
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        //與單一添加的 add 方法不同的是批量添加有返回值牲平,如果 numNew == 0 表示沒有要添加的元素則需要返回 false 
        return numNew != 0;
    }

add

在指定索引位置批量添加元素,該方法用的比較少域滥,下面是相關(guān)源碼:

    public boolean addAll(int index, Collection<? extends E> c) {
        //判斷索引位置越界
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        //判斷是否需要擴容纵柿,如果需要則擴容
        ensureCapacityInternal(size + numNew);  // Increments modCount
        
        int numMoved = size - index;
        //這里做了判斷,如果要numMoved > 0 代表插入的位置在集合中間位置启绰,在 numMoved == 0最后位置 則表示要在數(shù)組末尾添加 昂儒,如果 numMoved< 0,  rangeCheckForAdd 就跑出了角標(biāo)越界
        if (numMoved > 0)
            //將從index開始的數(shù)組元素往后移動numNew個位置
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
        //將a中的元素酬土,添加到elementData中
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

這里與單個添加元素的add方法不同的是這里有返回值荆忍。在代碼中對numMoved做了判斷,如果numMoved>0撤缴,則說明要從數(shù)組中間添加元素,那么才需要將指定位置開始的元素都后移numNew個位置叽唱。如果numMoved=0的話屈呕,怎么直接在數(shù)組的末尾位置添加元素即可。

ArrayList 刪除元素

    public E remove(int index) {
        //檢查角標(biāo)是否越界
        rangeCheck(index);

        modCount++;
        //獲取要刪除的角標(biāo)元素的值
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            //復(fù)制數(shù)組元素棺亭,覆蓋角標(biāo)元素
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //將數(shù)組末尾元素置為空虎眨,size減1
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

具體刪除邏輯可以看上面代碼注釋,這里需要注意的一點是:rangeCheck 和 rangeCheckForAdd 方法不完全想同 ,rangeCheck 只檢查了 index是否大于等于 size嗽桩,因為我們知道 size 為elementData 已存儲數(shù)據(jù)的個數(shù)岳守,我們只能移除 elementData 數(shù)組中 [0 , size -1] 的元素,否則應(yīng)該拋出角標(biāo)越界碌冶。但是為什么沒有 和 rangeCheckForAdd 一樣檢查小于0的角標(biāo)呢湿痢,是不是remove(-1) 不會拋異常呢? 其實不是的扑庞,因為 rangeCheck(index); 后我們?nèi)フ{(diào)用 elementData(index) 的時候也會拋出 IndexOutOfBoundsException 的異常譬重,這是數(shù)組本身拋出的,不是 ArrayList 拋出的罐氨。
下面我們來看看移除指定元素的方法:

 public boolean remove(Object o) {
        //如果要刪除元素為null
        if (o == null) {
            //循環(huán)查找結(jié)果為null的元素臀规,并刪除,返回true
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            //否則循環(huán)查找數(shù)組中值與o相等的值栅隐,并刪除塔嬉,返回true
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        //否則返回false
        return false;
    }

    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            //從指定角標(biāo)位置后一位開始,所有元素前移一位租悄,覆蓋原角標(biāo)index處的元素邑遏。
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

具體代碼邏輯請看如上代碼中的注釋,可以發(fā)現(xiàn)恰矩,移除指定位置元素和指定值的元素记盒,其最終底層都是通過System.arraycopy方法,將原有數(shù)組從index+1位置往后所有元素前移1位外傅,并釋放原來位于size處的元素纪吮。

removeAll/retainAll

ArrayList提供了removeAll/retainAll方法,這兩個方法的作用分別是:

  • removeAll萎胰,移除集合中與入?yún)⒓瞎蚕淼脑?/li>
  • retainAll碾盟,獲取集合中與入?yún)⒓瞎蚕淼脑?/li>
public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, false);
}

public boolean retainAll(Collection<?> c) {
    //判斷c是否為空,空則拋出NullPointException異常
    Objects.requireNonNull(c);
    return batchRemove(c, true);
}

private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;
    boolean modified = false;
    try {
        for (; r < size; r++)
            //這里根據(jù)傳入的complement值做不同的判斷技竟,如果傳入true冰肴,那么兩個集合都有,就保存榔组,否則放棄熙尉;如果傳入false,那么c中不包含elementData中的數(shù)組則保留。
            if (c.contains(elementData[r]) == complement)
                elementData[w++] = elementData[r];
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
        // 如果c.contains(o)拋出異常搓扯,拋出異常后 r!=size 則將 r 之后的元素不在比較直接放入數(shù)組
        if (r != size) {
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;
        }
        // 如果集合移除過元素检痰,則需要將 w 之后的元素設(shè)置為 null 釋放內(nèi)存
        if (w != size) {
            // clear to let GC do its work
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
    return modified;
}

從上面的源碼可以看出,removeAll與retainAll最終都調(diào)用了 batchRemove(Collection<?> c, boolean complement) 方法锨推,其不同之處在于铅歼,removeALL傳入的complement值為false,而retainAll傳入的為true公壤。
下面具體寫下batchRemove的邏輯:
1、從0開始遍歷elementData中的元素椎椰,判斷elementData中厦幅,r位置元素在c中是否存在,如果complement為true慨飘,那么存在則保留确憨,如果complement為false,那么不存在則保留套媚。
2缚态、由于c.contains(o) 方法可能會拋出NullPointException或者ClassCastException異常,如果出現(xiàn)異常而終止循環(huán)堤瘤,那么r會不等有size玫芦,這時候,r后面的元素則不再比較本辐,直接保存桥帆,這會導(dǎo)致返回結(jié)果不一定準(zhǔn)確。

ArrayList改查方法

由于這些方法比較簡單慎皱,在這里就不贅述了老虫,直接源碼。
查詢指定位置元素:

    public E get(int index) {
        //判斷index角標(biāo)是否合法
        rangeCheck(index);
        //返回指定角標(biāo)位置元素
        return elementData(index);
    }

查詢是否包含指定元素:

    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

修改指定位置元素:

    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

ArrayList遍歷

迭代器

迭代器Iterator模式是用于遍歷各種集合類的標(biāo)準(zhǔn)訪問方法茫多。他可以把訪問邏輯從不同類型的集合類中抽象出來祈匙,從而避免向客戶端暴露集合的內(nèi)部結(jié)構(gòu)。ArrayList作為集合類也不例外天揖,迭代器本身只提供三個接口方法夺欲,如下:

public interface Iterator<E> {
    //是否還有下一個元素
    boolean hasNext();
    //返回當(dāng)前元素
    E next();
    //移除一個當(dāng)前元素,也就是next元素
    void remove() 
}

ArrayList中調(diào)用iterator()將會返回一個內(nèi)部類Itr,其實現(xiàn)了Iterator接口今膊。

    public Iterator<E> iterator() {
        return new Itr();
    }

下面我們來看下這個內(nèi)部類的實現(xiàn):

private class Itr implements Iterator<E> {
        //游標(biāo)初始值為0
        int cursor;       // index of next element to return
        //下一個返回的元素角標(biāo)
        int lastRet = -1; // index of last element returned; -1 if no such
        //初始化的時候?qū)⑵滟x值為當(dāng)前集合的操作數(shù)
        int expectedModCount = modCount;
        //判斷是否還有下一個元素些阅,當(dāng)游標(biāo)等于size時,表示當(dāng)前集合已經(jīng)遍歷完了斑唬。
        public boolean hasNext() {
            return cursor != size;
        }
        //過去當(dāng)前元素集合的方法市埋,next 返回當(dāng)前遍歷位置的元素,如果在調(diào)用 next 之前集合被修改恕刘,并且迭代器中的期望操作數(shù)并沒有改變缤谎,將會引發(fā)ConcurrentModificationException。
        //next 方法多次調(diào)用 checkForComodification 來檢驗這個條件是否成立雪营。
        @SuppressWarnings("unchecked")
        public E next() {
            //驗證當(dāng)前操作數(shù)與期望操作數(shù)是否相等弓千,如果不等則拋出異常
            checkForComodification();
            int i = cursor;
            //如果游標(biāo)集合長度,那么拋出異常
            if (i >= size)
                throw new NoSuchElementException();
            //獲取集合底層數(shù)組
            Object[] elementData = ArrayList.this.elementData;
            //如果游標(biāo)大于等于數(shù)組長度献起,則拋出異常
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            //返回元素
            return (E) elementData[lastRet = i];
        }
        //移除元素
        public void remove() {
            //如果上次返回元素的角標(biāo)洋访,小于0,那么拋出非法異常谴餐,比如操作者沒有調(diào)用 next 方法就調(diào)用了 remove 操作
            if (lastRet < 0)
                throw new IllegalStateException();
            //檢查操作數(shù)
            checkForComodification();

            try {
                //移除上次調(diào)用 next 訪問的元素
                ArrayList.this.remove(lastRet);
                //集合中少了一個元素姻政,所以游標(biāo)前移一位
                cursor = lastRet;
                //刪除元素后賦值-1,確保先前 remove 時候的判斷
                lastRet = -1;
                //修改期望操作數(shù)
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            ...
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

forEach方法

在java8中新增了很多比較好用的方法岂嗓,forEach方法就是一個例子,下面是源碼:

@Override
public void forEach(Consumer<? super E> action) {
    //檢查調(diào)用者傳進來的操作函數(shù)是否為空
   Objects.requireNonNull(action);
   //與迭代不同期望操作被賦值為 final 也就是 forEach 過程中不允許并發(fā)修改集合否則會拋出異常
   final int expectedModCount = modCount;
   @SuppressWarnings("unchecked")
   final E[] elementData = (E[]) this.elementData;
   final int size = this.size;
   //每次取元素之前判斷操作數(shù)汁展,確保操作正常
   for (int i=0; modCount == expectedModCount && i < size; i++) {
       action.accept(elementData[i]);
   }
   if (modCount != expectedModCount) {
       throw new ConcurrentModificationException();
   }
}

該方法具體業(yè)務(wù)邏輯看代碼中的注釋,在這里就不多解釋了厌殉,在java8之后食绿,使用這些方法,可以減少我們的代碼量公罕,也能增加代碼可讀性器紧,在java8以后,使用這些方法屬于基本操作楼眷。

總結(jié)

  1. ArrayList底層是一個動態(tài)擴容數(shù)組铲汪,每次擴容需要增加1.5倍容量;
  2. ArrayList底層數(shù)組擴容是通過Arrays.copyof和System.arraycopy來實現(xiàn)的罐柳。每次都要產(chǎn)生新的數(shù)組掌腰,將原數(shù)組內(nèi)容拷貝到新數(shù)組,所以比較耗費性能张吉,因此在增刪改比較多的情況下齿梁,可以有限考慮LinkedList;
  3. ArrayList允許存放不止一個null值;
  4. ArrayList允許存放重復(fù)數(shù)據(jù)肮蛹,存儲順序按照元素的添加順序
  5. ArrayList線程不安全勺择;

注:本文參考:https://juejin.im/post/5ab548f75188257ddb0f8fa2#heading-29

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蔗崎,隨后出現(xiàn)的幾起案子酵幕,更是在濱河造成了極大的恐慌,老刑警劉巖缓苛,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芳撒,死亡現(xiàn)場離奇詭異,居然都是意外死亡未桥,警方通過查閱死者的電腦和手機笔刹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冬耿,“玉大人舌菜,你說我怎么就攤上這事∫嘞猓” “怎么了日月?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵袱瓮,是天一觀的道長。 經(jīng)常有香客問我爱咬,道長尺借,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任精拟,我火速辦了婚禮燎斩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蜂绎。我一直安慰自己栅表,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布师枣。 她就那樣靜靜地躺著怪瓶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪坛吁。 梳的紋絲不亂的頭發(fā)上劳殖,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音拨脉,去河邊找鬼哆姻。 笑死,一個胖子當(dāng)著我的面吹牛玫膀,可吹牛的內(nèi)容都是我干的矛缨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼帖旨,長吁一口氣:“原來是場噩夢啊……” “哼箕昭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起解阅,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤落竹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后货抄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體述召,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年蟹地,在試婚紗的時候發(fā)現(xiàn)自己被綠了积暖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡怪与,死狀恐怖夺刑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤遍愿,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布存淫,位于F島的核電站,受9級特大地震影響错览,放射性物質(zhì)發(fā)生泄漏纫雁。R本人自食惡果不足惜煌往,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一倾哺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刽脖,春花似錦羞海、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至院水,卻和暖如春腊徙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背檬某。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工撬腾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人恢恼。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓民傻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親场斑。 傳聞我的和親對象是個殘疾皇子漓踢,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355