深入ArrayList源碼分析(JDK1.8)

深入ArrayList源碼分析(JDK1.8)

Java 集合系列源碼分析文章:

ArrayList源碼分析(基于JDK8)

數(shù)據(jù)結(jié)構(gòu)中有兩種存儲(chǔ)結(jié)構(gòu)废岂,分別是:順序存儲(chǔ)結(jié)構(gòu)邻吭、鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)胃榕。在 Java 中喇勋,對(duì)于這四種結(jié)構(gòu)分別進(jìn)行實(shí)現(xiàn)的類有:

  • 順序存儲(chǔ)結(jié)構(gòu):ArrayList、Stack
  • 鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu):LinkedList别凤、Queue

這里只對(duì) ArrayList 的源碼進(jìn)行分析饰序,ArrayList 是一個(gè)數(shù)組隊(duì)列,相當(dāng)于 動(dòng)態(tài)數(shù)組 规哪。與Java 中的數(shù)組相比求豫,它的容量能動(dòng)態(tài)增長(zhǎng)。

特點(diǎn)

  • 基本的 ArrayList 常用于隨機(jī)訪問元素,但是在 List 中間插入和移除元素較慢蝠嘉;
  • ArrayList 中的操作不是線程安全的最疆。所以建議在單線程中才使用 ArrayList ,而在多線程中可以選擇 Vector 或者 CopyOnWriteArrayList 蚤告,建議使用 CopyOnWriteArrayList 努酸。

繼承關(guān)系

上面可以看到,ArrayList 實(shí)現(xiàn)來四個(gè)接口一個(gè)抽象類杜恰。它繼承類 AbstracList 抽象類获诈,實(shí)現(xiàn)了 ListRandomAccess (隨機(jī)訪問)心褐、 Cloneable (可克绿蛳选)、 Serializable (序列化)四個(gè)接口

  • ArrayList 繼承于 AbstractList 逗爹,實(shí)現(xiàn)了 List 亡嫌。它是一個(gè)數(shù)組隊(duì)列,提供了相關(guān)的添加掘而、刪除挟冠、修改、遍歷等功能袍睡;
  • ArrayList 實(shí)現(xiàn)了 RandmoAccess 接口知染,即提供了隨機(jī)訪問功能;
  • ArrayList 實(shí)現(xiàn)了 Cloneable 接口女蜈,即覆蓋了函數(shù) clone() ,能被克律瘛伪窖;
  • ArrayList 實(shí)現(xiàn)了 java.io.Serializable 接口,這意味著 ArrayList 支持序列化居兆,能通過序列化去傳輸覆山。

成員變量

ArrayList 的源碼中,主要成員變量如下:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
    ...
    // 默認(rèn)數(shù)組的長(zhǎng)度
    private static final int DEFAULT_CAPACITY = 10;
    // 默認(rèn)的空數(shù)組
    private static final Object[] EMPTY_ELEMENTDATA = {};
    // 默認(rèn)的空數(shù)組泥栖,與上面的區(qū)別簇宽,在不同的構(gòu)造函數(shù)中用到
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    // 真正用于存放數(shù)據(jù)的數(shù)組
    transient Object[] elementData;
    // 數(shù)組元素個(gè)數(shù)
    private int size;
    // 要分配的數(shù)組的最大大小,嘗試分配更大的陣列可能會(huì)導(dǎo)致 OutOfMemoryError
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    ...
}

ArrayList 的底層實(shí)現(xiàn)是數(shù)組吧享,默認(rèn)的數(shù)組大小是 10魏割。

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

  • ArrayList():構(gòu)造一個(gè)初始容量為10的空列表
  • ArrayList(Collection<?extend E> c):構(gòu)造一個(gè)包含指定元素的列表
  • ArrayList( int initialCapcity ):構(gòu)造一個(gè)具有初始容量值得空列表
// 第一種,調(diào)用ArrayList(10) 默認(rèn)初始化一個(gè)大小為10的Object數(shù)組
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 第二種
public ArrayList(int initialCapacity) {
    // 如果用戶初始化大小大于0钢颂,則新建一個(gè)用戶初始化值大小的Objec數(shù)組
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        // 等于0钞它,則賦值為變量EMPTY_ELEMENTDATA,默認(rèn)的空數(shù)組
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        // 小于0,則拋出異常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
    }
}
// 第三種:將容器數(shù)組化處理并將這個(gè)數(shù)組值賦給Object數(shù)組
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        // 當(dāng)c.toArray 返回的不是 Object 類型的數(shù)組時(shí)遭垛,進(jìn)行下面的轉(zhuǎn)化
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

增刪改查操作

對(duì)于增刪改查的基本操作尼桶,在這里只給出一些比較重要的源代碼進(jìn)行解析。

增加操作

  • add(E e):添加一個(gè)元素到列表的末尾锯仪。
  • add( int index, E element ) :在指定的位置添加元素
  • addAll( Collection<? extends E> c ):添加一個(gè)集合到元素的末尾.以上返回類型是boolean
  • ensureCapcity(int minCapcity):確保列表中含有minCapcity的最小容量

ArrayList默認(rèn)的插入是插在數(shù)組的末尾泵督,在不需要擴(kuò)容時(shí)是一個(gè)時(shí)間復(fù)雜度O(1)的操作,需要擴(kuò)容時(shí)復(fù)雜度為O(n)庶喜,所以如果預(yù)先能判斷數(shù)據(jù)量的大小小腊,可以指定初始化數(shù)組的大小,避免過多的擴(kuò)容操作溃卡。下面代碼看一些第一個(gè)增加元素的方法實(shí)現(xiàn):

// 第一步:
public boolean add(E e) {
    // 加入元素前檢查數(shù)組的容量是否足夠
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 第二步:
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    // 如果添加元素后大于當(dāng)前數(shù)組的長(zhǎng)度溢豆,則進(jìn)行擴(kuò)容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
// 第三步:擴(kuò)容
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // 將數(shù)組的長(zhǎng)度增加原來數(shù)組的一半,也就是1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 如果擴(kuò)充一半后仍然不夠瘸羡,則 newCapacity = minCapacity漩仙;minCapacity實(shí)際元素的個(gè)數(shù)
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 如果擴(kuò)充后的容量超過最大值變量MAX_ARRAY_SIZE
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    // 將elementData持有的元素復(fù)制到新的數(shù)組對(duì)象,最后將elementData的引用指向新的數(shù)組對(duì)象
    // 原有的數(shù)組對(duì)象因?yàn)闆]有了引用犹赖,一段時(shí)間后將被回收队他。
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

ArrayList還支持在指定索引處插入。在指定索引處插入時(shí)峻村,需要將指定索引及其之后的元素向后推一個(gè)位置麸折,所以是一個(gè)復(fù)雜度O(n)的操作。源碼如下:

// 第一步:
public void add(int index, E element) {
    // 檢查index的值是否在0到size之間粘昨,可以為size.
    rangeCheckForAdd(index);
    // 看 elementData 的長(zhǎng)度是否足夠垢啼,不夠擴(kuò)容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 將 elementData 從 index 開始后面的元素往后移一位
    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));
}

// 第三步:
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

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

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

刪除操作

  • remove(Object o):刪除列表中第一個(gè)出現(xiàn)O的元素
  • remove( int index):刪除列表中指定位置的元素
  • removeAll(Collection<?> c):刪除列表中包含C的所有元素
  • removeIf(Predictcate<? super E> filter):刪除列表中給定謂詞的所有元素
  • removeRange( int from,int to ):刪除從from到to的所有元素
  • clear():清除所有的元素。返回類型為void

ArrayList 刪除元素時(shí)张肾,需要將所刪元素之后位置的元素都向前推一個(gè)位置芭析,復(fù)雜度也是O(n)。所以ArrayList不適合需要頻繁在指定位置插入元素及刪除的應(yīng)用場(chǎng)景吞瞪,看代碼實(shí)現(xiàn):

public E remove(int index) {
    // 第一步:如果index >= size馁启,則拋出異常
    rangeCheck(index);

    modCount++;
    // 第二步:獲取刪除元素的值
    E oldValue = elementData(index);
    // 第三步:將 index 后面所有的元素往前移一位
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    elementData[--size] = null; // clear to let GC do its work
    // 第四步:返回要?jiǎng)h除的值
    return oldValue;
}

再看一個(gè)其它的實(shí)現(xiàn):

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                eturn true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

 /*
  * Private remove method that skips bounds checking and does not
  * return the value removed.
  */   
 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; // clear to let GC do its work
}

更改操作

  • retainAll( Collection<?> c ):僅僅保留列表中和C相同的元素,相當(dāng)于&運(yùn)算
  • set(int index,E element):用element替換index位置上的元素芍秆。
  • size():返回此列表的元素?cái)?shù)
  • sort(Comparator<? super E> c):按照指定的排序規(guī)則排序
  • subList( int from , int to ):返回從from到to之間的列表
  • toArray():將列表轉(zhuǎn)化為數(shù)組
  • trimToSize( ):修改當(dāng)前實(shí)例的容量是列表的當(dāng)前大小惯疙。

set方法

確保set的位置小于當(dāng)前數(shù)組的長(zhǎng)度(size)并且大于0,獲取指定位置(index)元素妖啥,然后放到oldValue存放霉颠,將需要設(shè)置的元素放到指定的位置(index)上,然后將原來位置上的元素oldValue返回給用戶荆虱。

// 第一步:
public E set(int index, E element) {
    // 檢查index是否大于size掉分,如果是則拋出異常
    rangeCheck(index);

    E oldValue = elementData(index);
    // 覆蓋ArrayList中index上的元素
    elementData[index] = element;
    // 返回被覆蓋的元素
    return oldValue;
}
// 第二步俭缓;
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

subList方法

我們看到代碼中是創(chuàng)建了一個(gè)ArrayList 類里面的一個(gè)內(nèi)部類SubList對(duì)象,傳入的值中第一個(gè)參數(shù)時(shí)this參數(shù)酥郭,其實(shí)可以理解為返回當(dāng)前l(fā)ist的部分視圖华坦,真實(shí)指向的存放數(shù)據(jù)內(nèi)容的地方還是同一個(gè)地方,如果修改了sublist返回的內(nèi)容的話不从,那么原來的list也會(huì)變動(dòng)惜姐。

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}

謹(jǐn)慎使用 ArrayList 中的 subList 方法

  • ArrayList 的 subList 結(jié)果不可強(qiáng)轉(zhuǎn)成 ArrayList,否則會(huì)拋出 ClassCastException 異常椿息,即 java.util.RandomAccessSubList cannot be cast to java.util.ArrayList. 說明:subList 返回的是 ArrayList 的內(nèi)部類 SubList歹袁,并不是 ArrayList ,而是 ArrayList 的一個(gè)視圖寝优,對(duì)于 SubList 子列表的所有操作最終會(huì)反映到原列表上条舔。
List<String> list = new ArrayList<>();
list.add("1");
list.add("1");
list.add("2");
ArrayList<String> strings =  (ArrayList)list.subList(0, 1);

運(yùn)行結(jié)果:Exception in thread "main" java.lang.ClassCastException: java.util.ArrayList$SubList cannot be cast to java.util.ArrayList  at com.nuih.List.ArrayListTest.main(ArrayListTest.java:29)
  • 在 subList 場(chǎng)景中,高度注意對(duì)原集合元素個(gè)數(shù)的修改乏矾,會(huì)導(dǎo)致子列表的遍歷孟抗、增加、 刪除均會(huì)產(chǎn) ConcurrentModificationException 異常钻心。
List<String> list = new ArrayList<>();
list.add("1");
list.add("1");
list.add("2");
List<String> subList =  list.subList(0, 1);
// 對(duì)原 List 增加一個(gè)值
list.add("10");
subList.add("11"); // 這一行會(huì)報(bào) java.util.ConcurrentModificationException

trimToSize方法

public void trimToSize() {
    // 修改次數(shù)加1
    modCount++;
    // 如果當(dāng)前元素小于數(shù)組容量凄硼,則將elementData中空余的空間(包括null值)去除
    // 例如:數(shù)組長(zhǎng)度為10,其中只有前三個(gè)元素有值捷沸,其他為空摊沉,那么調(diào)用該方法后,數(shù)組的長(zhǎng)度變?yōu)?
    if (size < elementData.length) {
        elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
    }
}

toArray方法

第一個(gè)痒给,Object[] toArray()方法说墨。該方法有可能會(huì)拋出java.lang.ClassCastException異常,如果直接用向下轉(zhuǎn)型的方法苍柏,將整個(gè)ArrayList集合轉(zhuǎn)變?yōu)橹付愋偷腁rray數(shù)組尼斧,便會(huì)拋出該異常,而如果轉(zhuǎn)化為Array數(shù)組時(shí)不向下轉(zhuǎn)型序仙,而是將每個(gè)元素向下轉(zhuǎn)型突颊,則不會(huì)拋出該異常鲁豪,顯然對(duì)數(shù)組中的元素一個(gè)個(gè)進(jìn)行向下轉(zhuǎn)型潘悼,效率不高,且不太方便爬橡。

第二個(gè)治唤, T[] toArray(T[] a)方法。該方法可以直接將ArrayList轉(zhuǎn)換得到的Array進(jìn)行整體向下轉(zhuǎn)型(轉(zhuǎn)型其實(shí)是在該方法的源碼中實(shí)現(xiàn)的)糙申,且從該方法的源碼中可以看出宾添,參數(shù)a的大小不足時(shí),內(nèi)部會(huì)調(diào)用Arrays.copyOf方法,該方法內(nèi)部創(chuàng)建一個(gè)新的數(shù)組返回缕陕,因此對(duì)該方法的常用形式如下:

public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}
// 第一種方式(最常用)
Integer[] integer = arrayList.toArray(new Integer[0]);

// 第二種方式(容易理解)
Integer[] integer1 = new Integer[arrayList.size()];
arrayList.toArray(integer1);

// 拋出異常粱锐,java不支持向下轉(zhuǎn)型
// Integer[] integer2 = new Integer[arrayList.size()];
// integer2 = arrayList.toArray();

查操作

  • contains(Object o):如果包含元素o,則返回為true
  • get(int index):返回指定索引的元素
  • indexOf( Object o ):返回此列表中指定元素的第一次出現(xiàn)的索引,如果列表不包含此元素扛邑,返回-1
  • lastindexOf( Object o ):返回此列表中指定元素的最后一次出現(xiàn)的索引怜浅,如果列表不包含此元素,返回-1
  • isEmpty():如果列表為空蔬崩,返回true
  • iterator():返回列表中元素的迭代器
  • listIterator():返回列表的列表迭代器(按適當(dāng)?shù)捻樞颍?/li>
  • listIterator(int index):從適當(dāng)?shù)奈恢梅祷亓斜淼牧斜淼鳎ò凑照_的順序)

get 方法

返回指定位置上的元素

public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

contains方法

調(diào)用indexOf方法恶座,遍歷數(shù)組中的每一個(gè)元素作對(duì)比,如果找到對(duì)于的元素沥阳,則返回true跨琳,沒有找到則返回false。

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;
}

iterator方法

interator方法返回的是一個(gè)內(nèi)部類桐罕,由于內(nèi)部類的創(chuàng)建默認(rèn)含有外部的this指針脉让,所以這個(gè)內(nèi)部類可以調(diào)用到外部類的屬性。

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

一般的話冈绊,調(diào)用完iterator之后侠鳄,我們會(huì)使用iterator做遍歷,這里使用next做遍歷的時(shí)候有個(gè)需要注意的地方死宣,就是調(diào)用next的時(shí)候伟恶,可能會(huì)引發(fā)ConcurrentModificationException,當(dāng)修改次數(shù)毅该,與期望的修改次數(shù)(調(diào)用iterator方法時(shí)候的修改次數(shù))不一致的時(shí)候博秫,會(huì)發(fā)生該異常,詳細(xì)我們看一下代碼實(shí)現(xiàn):

@SuppressWarnings("unchecked")
public E next() {
    checkForComodification();
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
}

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

expectedModCount這個(gè)值是在用戶調(diào)用ArrayList的iterator方法時(shí)候確定的眶掌,但是在這之后用戶add挡育,或者remove了ArrayList的元素,那么modCount就會(huì)改變朴爬,那么這個(gè)值就會(huì)不相等即寒,將會(huì)引發(fā)ConcurrentModificationException異常,這個(gè)是在多線程使用情況下召噩,比較常見的一個(gè)異常母赵。

遍歷

ArrayList 遍歷的四種方式

  • 第1種,普通for循環(huán)隨機(jī)訪問具滴,通過索引值去遍歷凹嘲。
// 隨機(jī)訪問
List<String> list = new ArrayList<>();
int size = list.size();
for (int i = 0; i < size; i++) {
    value = list.get(i);
}
  • 第2種,通過迭代器遍歷构韵。即通過Iterator去遍歷周蹭。
// 迭代器遍歷
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
    value = iter.next();
}
  • 第3種趋艘,for-each。
// 增強(qiáng)for循環(huán)
for (String s : list) {
    value = s;
}
  • 第4種 forEach + lambda 循環(huán)遍歷
list.forEach(p -> {
    p.hashCode();
});

性能對(duì)比

既然有 4 種遍歷凶朗,那我們看看哪種遍歷效率下面我們通過一個(gè)實(shí)驗(yàn)來看下這四種循環(huán)的耗時(shí)吧:測(cè)試代碼

package com.nuih.List;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;

public class ArrayListTest {
    public static void main(String[] args) {
        // 數(shù)據(jù)預(yù)熱
        List<String> testList = createTestList(10);
        testForEach(testList);
        testFor(testList);
        testRandFor(10, testList);
        
        List<Integer> integers = Arrays.asList(10, 50, 100, 500, 1000, 10000, 50000, 100000, 5000000, 10000000, 30000000);
        for (Integer i : integers) {
            testRand(i);
        }
    }

    private static void testRand(int size) {
        System.out.println("-----------次數(shù):" + size + "------------");
        List<String> list = createTestList(size);
        // 隨機(jī)訪問通過索引值去遍歷瓷胧。
        long time1 = System.nanoTime();
        testRandFor(size, list);
        long time2 = System.nanoTime();
        // 增強(qiáng) for 循環(huán)
        testFor(list);
        long time3 = System.nanoTime();
        // 迭代器遍歷
        testIterator(list);
        long time4 = System.nanoTime();
        // forEach + lambda
        testForEach(list);
        long time5 = System.nanoTime();
        System.out.println("隨機(jī)訪問\t\t" + (time2 - time1) / 1000 + " ms");
        System.out.println("增強(qiáng) for 遍歷\t\t" + (time3 - time2) / 1000 + " ms");
        System.out.println("迭代器遍歷\t\t" + (time4 - time3) / 1000 + " ms");
        System.out.println("forEach 遍歷\t\t" + (time5 - time4) / 1000 + " ms");
        System.out.println();
    }

    private static void testRandFor(int size, List<String> list) {
        for (int i = 0; i < size; i++) {
            list.get(i).hashCode();
        }
    }

    private static void testFor(List<String> list) {
        for (String s : list) {
            s.hashCode();
        }
    }

    private static void testIterator(List<String> list) {
        Iterator<String> iter = list.iterator();
        while (iter.hasNext()) {
            iter.next().hashCode();
        }
    }

    private static void testForEach(List<String> list) {
        list.forEach(p -> {
            p.hashCode();
        });
    }

    public static List<String> createTestList(int size) {
        List<String> list = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            list.add(UUID.randomUUID().toString());
        }
        return list;
    }
}

測(cè)試結(jié)果:

結(jié)論:如果數(shù)據(jù)量比較少的話貌似四種循環(huán)耗時(shí)都差不多,但是隨著數(shù)據(jù)量的增長(zhǎng)會(huì)發(fā)現(xiàn) foreach 的效率是最好的棚愤。但是從上面我們會(huì)發(fā)現(xiàn)一個(gè)奇怪的現(xiàn)象抖单,第一次循環(huán)的時(shí)候forEach 遍歷的時(shí)間是最長(zhǎng)的盡管數(shù)據(jù)量非常少也會(huì)這樣。但是后面的耗時(shí)就正常了遇八。如果放開測(cè)試?yán)锩娴念A(yù)熱代碼矛绘,每次跑出來的耗時(shí)也是正常的。

-----------次數(shù):10------------
隨機(jī)訪問        15 ms
增強(qiáng) for 遍歷       8 ms
迭代器遍歷       6 ms
forEach 遍歷      65728 ms

-----------次數(shù):50------------
隨機(jī)訪問        16 ms
增強(qiáng) for 遍歷       21 ms
迭代器遍歷       13 ms
forEach 遍歷      10 ms

-----------次數(shù):100------------
隨機(jī)訪問        7 ms
增強(qiáng) for 遍歷       23 ms
迭代器遍歷       34 ms
forEach 遍歷      19 ms

-----------次數(shù):500------------
隨機(jī)訪問        64 ms
增強(qiáng) for 遍歷       47 ms
迭代器遍歷       39 ms
forEach 遍歷      105 ms

-----------次數(shù):1000------------
隨機(jī)訪問        129 ms
增強(qiáng) for 遍歷       99 ms
迭代器遍歷       81 ms
forEach 遍歷      58 ms

-----------次數(shù):10000------------
隨機(jī)訪問        1748 ms
增強(qiáng) for 遍歷       1921 ms
迭代器遍歷       1270 ms
forEach 遍歷      2212 ms

-----------次數(shù):50000------------
隨機(jī)訪問        4013 ms
增強(qiáng) for 遍歷       2739 ms
迭代器遍歷       3628 ms
forEach 遍歷      2368 ms

-----------次數(shù):100000------------
隨機(jī)訪問        9874 ms
增強(qiáng) for 遍歷       4500 ms
迭代器遍歷       5159 ms
forEach 遍歷      6232 ms

-----------次數(shù):5000000------------
隨機(jī)訪問        215933 ms
增強(qiáng) for 遍歷       27000 ms
迭代器遍歷       26586 ms
forEach 遍歷      22105 ms

-----------次數(shù):10000000------------
隨機(jī)訪問        379588 ms
增強(qiáng) for 遍歷       57104 ms
迭代器遍歷       42973 ms
forEach 遍歷      40539 ms

-----------次數(shù):30000000------------
隨機(jī)訪問        1090531 ms
增強(qiáng) for 遍歷       195013 ms
迭代器遍歷       185519 ms
forEach 遍歷      151925 ms

ArrayList 刪除數(shù)據(jù)

雖然有四種遍歷方式刃永,但是能夠正確刪除數(shù)據(jù)的方式只有兩種

  • 第 1 種通過迭代器進(jìn)行刪除货矮。這種方式的話,也是《阿里代碼規(guī)約》所推薦的斯够。
Iterator<String> iter = list.iterator();        
while (iter.hasNext()) {
    iter.next().hashCode();
    iter.remove();
}
  • 第 2 種倒序循環(huán)刪除
for(int i = list.size()-1;i>=0;i--) {
    list.remove(i);
}

下面再演示下錯(cuò)誤的刪除操作

  • 普通 for 循環(huán)正序刪除囚玫,刪除過程中元素向左移動(dòng),不能刪除重復(fù)的元素
List<String> list = new ArrayList<>();
list.add("1");
list.add("1");
list.add("2");
for(int i=0;i<list.size();i++){
    list.remove(i);
}        
System.out.println(String.join(",",list));

結(jié)果輸出:1

  • 增強(qiáng) for 循環(huán)刪除會(huì)拋出 java.util.ConcurrentModificationException
List<String> list = new ArrayList<>();
list.add("1");
list.add("1");
list.add("2");
for (String s : list) {
    list.remove(s);
}        
System.out.println(String.join(",",list));

ArrayList ConcurrentModificationException

ConcurrentModificationException 出現(xiàn)在使用 ForEach遍歷读规,迭代器遍歷的同時(shí)抓督,進(jìn)行刪除,增加出現(xiàn)的異常束亏。平常使用的ArrayList, HashMap都有可能拋出這種異常铃在,粗心的話,很容易犯這種錯(cuò)誤碍遍,導(dǎo)致線上事故定铜!

下面總結(jié)下ArrayList的一些使用場(chǎng)景,來討論是否會(huì)拋出ConcurrentModificationException

For..i 遍歷

這個(gè)遍歷的意思怕敬,是指 for(int i = 0 ; i <list.size(); i++)這種使用下標(biāo)進(jìn)行遍歷的方式揣炕。
這種情形下,增加都不會(huì)有 ConcurrentModificationException东跪。但是也可能導(dǎo)致另外的一些問題畸陡,比如下面這段代碼,會(huì)死循環(huán)

List<Integer> list = new Arraylist<>();
list.add(1);
list.add(2);
list.add(3);
for(int i = 0;i<list.size();i++){
   list.add(i);  
}

遍歷刪除的情況下虽填,不會(huì)有ConcurrentModificationException丁恭,但是要注意代碼,防止數(shù)組越界異常卤唉。下面這種形式的代碼會(huì)拋出數(shù)組越界異常涩惑。

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
int length = list.size();
for(int i = 0;i<length;i++){
      list.remove(i);
}

ForEach 遍歷

ForEach 遍歷就是 For(Object o : List<Object>) 這種遍歷方式仁期,眾所周知桑驱,F(xiàn)orEach循環(huán)只是JAVA的一個(gè)語法糖竭恬,在字節(jié)碼層面上,等同于迭代器循環(huán)遍歷熬的。在這種情形下痊硕,增加元素一定會(huì)拋出ConcurrentModificationException,
而刪除元素在大多數(shù)情況下押框,會(huì)拋出ConcurrentModificationException(小知識(shí)岔绸,當(dāng)且僅當(dāng)刪除小標(biāo)為 size()-2,也就是倒數(shù)第二個(gè)元素的時(shí)候橡伞,不會(huì)拋出異常)盒揉。
這種情況下,會(huì)有異常拋出

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
for (Integer i : list) {
    if(i == 1){
        list.remove(i);
    }
}

可以修改上面的判斷語句兑徘, i == 1 修改為 i == 2 則不會(huì)拋出異常刚盈。

subList

在 subList 場(chǎng)景中,高度注意對(duì)原集合元素個(gè)數(shù)的修改挂脑,會(huì)導(dǎo)致子列表的遍歷藕漱、增加、 刪除均會(huì)產(chǎn) ConcurrentModificationException 異常崭闲。

List<String> list = new ArrayList<>();
list.add("1");
list.add("1");
list.add("2");
List<String> subList =  list.subList(0, 1);
// 對(duì)原 List 增加一個(gè)值
list.add("10");
subList.add("11"); // 這一行會(huì)報(bào) java.util.ConcurrentModificationException

如何避免ConcurrentModificationException

  1. 需要遍歷新增時(shí)肋联,最好new一個(gè)和老List相同的臨時(shí)List,遍歷老的List刁俭,然后在臨時(shí)List上進(jìn)行元素的增加
  2. 需要進(jìn)行刪除時(shí)橄仍,使用迭代器刪除(iterator.remove()),而不是直接調(diào)用 list.remove()
    3.小心牍戚,謹(jǐn)慎

總結(jié)

ArrayList底層采用數(shù)組實(shí)現(xiàn)沙兰,是一個(gè)用于持有元素的有序、元素可重復(fù)的容器翘魄。適用于需要查找指定索引處元素的場(chǎng)景鼎天。當(dāng)需要頻繁插入、刪除元素暑竟,或者查找指定元素時(shí)斋射,其復(fù)雜度為O(n)。

  • 初始化 List 的時(shí)候盡量指定它的容量大小但荤。(盡量減少擴(kuò)容次數(shù))
  • 當(dāng)使用無參數(shù)構(gòu)造函數(shù)創(chuàng)建ArrayList對(duì)象時(shí)罗岖,ArrayList對(duì)象中的數(shù)組初始長(zhǎng)度為0(是一個(gè)空數(shù)組)。
  • ArrayList的擴(kuò)容策略是每次都增加當(dāng)前數(shù)組長(zhǎng)度的一半(非固定分配)腹躁。
  • ArrayList的擴(kuò)容方式是直接創(chuàng)建一個(gè)新的數(shù)組桑包,并將數(shù)據(jù)拷貝到新數(shù)組中。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末纺非,一起剝皮案震驚了整個(gè)濱河市哑了,隨后出現(xiàn)的幾起案子赘方,更是在濱河造成了極大的恐慌,老刑警劉巖弱左,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窄陡,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡拆火,警方通過查閱死者的電腦和手機(jī)跳夭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來们镜,“玉大人币叹,你說我怎么就攤上這事∧O粒” “怎么了套硼?”我有些...
    開封第一講書人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)胞皱。 經(jīng)常有香客問我邪意,道長(zhǎng),這世上最難降的妖魔是什么反砌? 我笑而不...
    開封第一講書人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任雾鬼,我火速辦了婚禮,結(jié)果婚禮上宴树,老公的妹妹穿的比我還像新娘策菜。我一直安慰自己,他們只是感情好酒贬,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開白布又憨。 她就那樣靜靜地躺著,像睡著了一般锭吨。 火紅的嫁衣襯著肌膚如雪蠢莺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,842評(píng)論 1 290
  • 那天零如,我揣著相機(jī)與錄音躏将,去河邊找鬼。 笑死考蕾,一個(gè)胖子當(dāng)著我的面吹牛祸憋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肖卧,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼蚯窥,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拦赠,我...
    開封第一講書人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤巍沙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后矛紫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡牌里,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年颊咬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片牡辽。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡喳篇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出态辛,到底是詐尸還是另有隱情麸澜,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布奏黑,位于F島的核電站炊邦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏熟史。R本人自食惡果不足惜馁害,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蹂匹。 院中可真熱鬧碘菜,春花似錦、人聲如沸限寞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽履植。三九已至计雌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間玫霎,已是汗流浹背白粉。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鼠渺,地道東北人鸭巴。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像拦盹,于是被迫代替她去往敵國和親鹃祖。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349