概述
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):
- 其中AbstractList和List<E>是規(guī)定了ArrayList作為一個集合框架所必須要具備的一些屬性和方法锹漱,這里面就包括了我們后面要分析的增刪改查一類箭养。
- 其次ArrayList實現(xiàn)RandomAccess接口表示它支持隨機快速訪問,通過get(index)查找ArrayList中的元素時間復(fù)雜度是O(1)哥牍,有些類則不具備這一特性毕泌,例如LinkedList;
- ArrayList實現(xiàn)Cloneable接口說明它支持被克隆/復(fù)制,其內(nèi)部提供了clone()方法供使用者調(diào)用來對ArrayList進行賦值嗅辣,但是其實現(xiàn)僅僅通過Arrays.copyOf完成了對ArrayList的淺復(fù)制撼泛,即當(dāng)arrayList中保存的是引用類型變量時,改變原ArrayList的數(shù)據(jù)澡谭,克隆的ArrayList隨之改變愿题。
- 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é)出下面比較重要的兩點:
- 每次擴容默终,會先擴容為原數(shù)組長度的1.5倍椅棺,如果擴容后的長度還是小于所需的最小容量抡诞,那么直接擴容到所需最小容量。
- 擴容的過程土陪,其實是將原數(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é)
- ArrayList底層是一個動態(tài)擴容數(shù)組铲汪,每次擴容需要增加1.5倍容量;
- ArrayList底層數(shù)組擴容是通過Arrays.copyof和System.arraycopy來實現(xiàn)的罐柳。每次都要產(chǎn)生新的數(shù)組掌腰,將原數(shù)組內(nèi)容拷貝到新數(shù)組,所以比較耗費性能张吉,因此在增刪改比較多的情況下齿梁,可以有限考慮LinkedList;
- ArrayList允許存放不止一個null值;
- ArrayList允許存放重復(fù)數(shù)據(jù)肮蛹,存儲順序按照元素的添加順序
- ArrayList線程不安全勺择;
注:本文參考:https://juejin.im/post/5ab548f75188257ddb0f8fa2#heading-29