1. 簡介
ArrayList可以說是我們最常用的一種集合了。
他的本質是一個數組,一個可以自動擴容的動態數組,線程不安全,允許元素爲null。
由於數組的內存連續,可以根據下標以O(1)的時間讀寫元素,因此時間效率很高。
2. 內部屬性
我們先來看下ArrayList裏面有哪幾個屬性:
-
private static final long serialVersionUID = 8683452581122892189L;
序列話UID。由於ArrayList實現了Serializable接口,爲了序列化和反序列化的方便,我們就手動爲他添加一個序列化UID。 -
private static final int DEFAULT_CAPACITY = 10;
默認的容量。 -
private static final Object[] EMPTY_ELEMENTDATA = {};
空數組。 -
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
默認的空數組。 -
transient Object[] elementData;
真正存放元素的數組。 -
private int size;
當前元素個數。
3. 構造方法
// 傳入參數爲初始化容量時的構造方法
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
// 如果傳入參數大於零,那就創建一個對應大小的數組
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 如果傳入參數等於0,那就直接把屬性中創建好的空數組複製
this.elementData = EMPTY_ELEMENTDATA;
} else {
// 如果傳入參數小於0,那就拋異常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
// 傳入參數爲空時的構造方法
public ArrayList() {
// 將屬性中創建好的空數組複製過來
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 傳入參數爲數組時的構造方法
public ArrayList(Collection<? extends E> c) {
// 先轉換爲數組
elementData = c.toArray();
// 因爲size代表ArrayList中元素個數,所以要把數組的長度賦過來
// 如果個數不爲0進入此if
if ((size = elementData.length) != 0) {
//這裏是當c.toArray出錯,沒有返回Object[]時,利用Arrays.copyOf 來複制集合c中的元素到elementData數組中
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 如果個數爲0,那就把屬性中的空數組複製過來
this.elementData = EMPTY_ELEMENTDATA;
}
}
4. 常用API
4.1 增
ArrayList # add(E e)
public boolean add(E e) {
// ->> 分析4.1.1
ensureCapacityInternal(size + 1); // Increments modCount!!
// 將需要加入的元素加到最後面
elementData[size++] = e;
return true;
}
/**
* 分析4.1.1 ensureCapacityInternal()
*/
private void ensureCapacityInternal(int minCapacity) {
// DEFAULTCAPACITY_EMPTY_ELEMENTDATA這個變量只有在通過無參構造的時候用到過
// 也就是說,他判斷創建出來的這個數組,到底是不是無參構造方法創建出來的,如果是就找出DEFAULT_CAPACITY和minCapacity中較大的那個
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 但是按道理來說,add一個元素minCapacity肯定爲1,肯定小於DEFAULT_CAPACITY,爲什麼還要做一個判斷呢?
// 但是你得注意,還有AddAll這個方法
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// ->> 分析4.1.2
ensureExplicitCapacity(minCapacity);
}
/**
* 分析4.1.2 ensureExplicitCapacity()
*/
private void ensureExplicitCapacity(int minCapacity) {
// modCount是AbstractList中的屬性,如果需要擴容,則會修改modCount
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
// ->> 分析4.1.3
grow(minCapacity);
}
/**
* 分析4.1.3 grow()
* 作用:擴容
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 擴容爲原數組的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果還不夠,就直接用能容納的最小大小
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
// 如果新數組比MAX_ARRAY_SIZE還要大的話
if (newCapacity - MAX_ARRAY_SIZE > 0)
// ->> 分析4.1.4
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually clos2019e to size, so this is a win:
// 生成新數組
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
* 分析4.1.4
*/
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 # add(E e)
// 按給定的位置添加指定元素
public void add(int index, E element) {
// 如果給的位置超過了集合已存放的元素的個數或者小於0,就拋異常
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
// ->> 分析4.1.1
ensureCapacityInternal(size + 1); // Increments modCount!!
// 將elementData從index處分開,給index空出位置
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 放入數據
elementData[index] = element;
size++;
}
ArrayList # addAll(Collection<? extends E> c)
public boolean addAll(Collection<? extends E> c) {
// 轉爲數組
Object[] a = c.toArray();
int numNew = a.length;
// 擴容數組 ->> 分析4.1.1
ensureCapacityInternal(size + numNew); // Increments modCount
// 將a數組的全部內容添加到elementData數組從size開始之後
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
ArrayList # addAll(int index, Collection<? extends E> c)
public boolean addAll(int index, Collection<? extends E> c) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
// 轉爲數組
Object[] a = c.toArray();
int numNew = a.length;
// 擴容 ->> 分析4.1.1
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
// 如果index小於size,也就是說需要在原數組的中間插入的haul,就需要先把原數組index之後的數據往後移動
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
總結
- 無論是add還是addAll,都是先判斷是否越界,如果越界就擴容,然後再移動數組
- 如果需要擴容,默認擴容原來的一般大小;如果還不夠,那就直接將目標的size作爲擴容後的大小
- 在擴容成功後,會修改modCount
4.2 刪
ArrayList # remove(int index)
public E remove(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
// 修改modCount
modCount++;
// 將要刪除的內容先保存下來
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
// 判斷要刪除的數據是不是最後一位
// 如果不是最後一位,還得先把後面的數據往前移動
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 清除數據,更改引用,讓GC去清理
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
ArrayList # remove(Object o)
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
// ->> 分析4.2.1
fastRemove(index);
return true;
}
} else {
// 如果參數不爲空,那就遍歷整個數組集合
for (int index = 0; index < size; index++)
// 找到和參數相等的那一位,然後將該位移除
if (o.equals(elementData[index])) {
// ->> 分析4.2.1
fastRemove(index);
return true;
}
}
return false;
}
/**
* 分析4.2.1:fastRemove()
* 作用:基本上和remove(int index)一樣
*/
private void fastRemove(int index) {
// 修改modCount
modCount++;
int numMoved = size - index - 1;
// 判斷要刪除的數據是不是最後一位
// 如果不是最後一位,還得先把後面的數據往前移動
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 清除數據,更改引用,讓GC去清理
elementData[--size] = null; // clear to let GC do its work
}
ArrayList # removeAll(Collection<?> c)
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
// ->> 分析4.2.2
return batchRemove(c, false);
}
/**
* 分析4.2.2 batchRemove()
*/
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++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
// 如果出現異常會導致r!=size,就把異常之後的數據全部覆蓋到數組裏面
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
// 將後面的元素全部置空,讓GC來回收
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
這塊我看的時候有點繞,但是一想通就好了。
其實這個地方是要把C集合中和原集合中共有的元素刪除,那我就只需要遍歷原數組,然後碰到和C集合相同的元素就直接放到前面去,然後等遍歷完成後,一次性把後面的全部置空。
ArrayList # retainAll(Collection<?> c)
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
// ->> 分析4.2.2
return batchRemove(c, true);
}
ArrayList # clear()
public void clear() {
modCount++;
// clear to let GC do its work
// 直接遍歷每一位,然後把每一位都置空,讓GC去清理
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
總結
- 所有的刪除操作都會修改modCount
4.3 改
public E set(int index, E element) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
// 取出原來的元素
E oldValue = (E) elementData[index];
// 把需要更改的數據放進去
elementData[index] = element;
return oldValue;
}
沒啥好分析的
不需要修改modCount,相對高效
4.4 查
public E get(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
return (E) elementData[index];
}
沒啥好分析的
不需要修改modCount,相對高效
4.5 包括 contains() & indexOf()
public boolean contains(Object o) {
// ->> 分析4.5.1
return indexOf(o) >= 0;
}
/**
* 分析4.5.1:indexOf()
*/
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;
}
沒啥好分析的
4.6 判空 isEmpty()
public boolean isEmpty() {
return size == 0;
}
沒啥好分析的
4.7 縮容 trimToSize()
public void trimToSize() {
modCount++;
if (size < elementData.length) {
// 如果size==0的時候,就直接把EMPTY_ELEMENTDATA複製過去
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
5. 迭代器 Iterator
5.1 創建迭代器
public Iterator<E> iterator() {
// 構造Itr對象並返回
return new Itr();
}
5.2 Itr屬性
// 限制,也就是數組的元素個數
protected int limit = ArrayList.this.size;
// 下一個元素的下標
int cursor; // index of next element to return
// 上一次返回元素的下標
int lastRet = -1; // index of last element returned; -1 if no such
// 用於判斷集合是否修改過結構的標誌
int expectedModCount = modCount;
5.3 Itr # hasNext()
public boolean hasNext() {
return cursor < limit;
}
不用多說
5.4 Itr # next()
public E next() {
// 判斷是否修改過List的結構,如果修改了就拋異常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
int i = cursor;
// 如果越界了就拋異常
if (i >= limit)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
// 再次判斷是否越界,在 我們這裏的操作時,有異步線程修改了List
if (i >= elementData.length)
throw new ConcurrentModificationException();
// 標記加1
cursor = i + 1;
// 返回數據,並設置上一次的下標
return (E) elementData[lastRet = i];
}
5.5 Itr # remove()
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
try {
// 調用ArrayList的remove方法移除數據
ArrayList.this.remove(lastRet);
// 更新一系列數據
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
limit--;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
6. 總結
6.1 modCount到底是個什麼東西?
如果你看過很多集合源碼的話,你就會發現你會在很多地方都會碰到這個modCount,例如ArrayList、LinkedList、HashMap等等。modCount字面意思就是修改次數,那麼爲什麼需要紀錄這個修改次數呢?
你回想下,似乎用到modCount的地方,如ArrayList、LinkedList等等,他們都用一個共性——線程不安全。
而在你看了這幾個類的迭代器後就會發現,他們迭代器中一定有一個屬性(如我們上面的expectedModCount)初始化時的值就是modCount的值。
然後在迭代器的方法中,只要是遍歷這個集合的時候,都需要將兩個值進行對比,然後再移除元素的時候,都需要更新迭代器裏面的modCount變量。
這時,你需要了解下Fail-Fast機制。
我們都知道這些集合是線程不安全的,如果在使用迭代器的過程中,有其他線程對集合進行了修改,那麼就會拋出ConcurrentModificationException異常,這就是Fail-Fast策略。而這個時候源碼中就通過modCount進行了操作。迭代器在創建時,會創建一個變量等於當時的modCount,如果在迭代過程中,集合發生了變化,modCount就是++。這時迭代器中的變量的值和modCount不相等了,那就拋異常。
所以,遍歷線程不安全的集合時,儘量使用迭代器
解決辦法:
- 在遍歷過程中所有涉及到改變 modCount 值得地方全部加上 synchronized 或者直接使用 Collections.synchronizedList,這樣就可以解決。但是不推薦,因爲增刪造成的同步鎖可能會阻塞遍歷操作。
- 使用 CopyOnWriteArrayList 來替換 ArrayList。推薦使用該方案。關於CopyOnWriteArrayList的內容此處不再過多的去將,想了解的同學可以百度或者谷歌。
6.2 ArrayList和Vector的區別
- ArrayList線程不安全,Vector線程安全
- 擴容的時候ArrayList默認擴容1.5倍,Vector默認擴容1倍