搞懂Java ArrayList原碼
文章目錄
概述
ArrayList的基本特點
- ArrayList 底層是一個動態擴容的陣列結構
- 允許存放(不止一個) null 元素
- 允許存放重複資料,儲存順序按照元素的新增順序
- ArrayList 並不是一個執行緒安全的集合。如果集合的增刪操作需要保證執行緒的安全性,可以考慮使用
CopyOnWriteArrayList
或者使用collections.synchronizedList(List l)
函式返回一個執行緒安全的ArrayList類.
繼承關係
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList 繼承自 AbstractList
,實現了List, RandomAccess, Cloneable, java.io.Serializable
介面(4介面1父類)。
構造方法
- 無參構造方法
容量為0,呼叫add方法後擴為10 - 指定初始容量的構造方法
如果事先知道資料的大小,就可以構造指定容量的list,後面不需要擴容,會節省很多開銷(陣列拷貝),避免浪費記憶體空間(每次擴容為原先的1.5倍) - 使用另個一個集合 Collection 的構造方法
這裡注意程式碼elementData.getClass() != Object[].class
的判斷,ArrayList的elementData是Object[]型別,而傳入的collection物件可能不是(如Lists.asList(“a”,“b”)
的toArray()結果是String[]),在賦值時,就會做判斷,向上升級.
transient Object[] elementData; // non-private to simplify nested class access
...
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
...
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
新增元素,擴容機制
擴容
//擴容檢查
private void ensureCapacityInternal(int minCapacity) {
//如果是無參構造方法構造的的集合,第一次新增元素的時候會滿足這個條件 minCapacity 將會被賦值為 10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 將 size + 1 或 10 傳入 ensureExplicitCapacity 進行擴容判斷
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
//運算元加 1 用於保證併發訪問
modCount++;
// 如果 當前陣列的長度比新增元素後所需的長度要小則進行擴容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
上面的原始碼主要做了擴容前的判斷操作.
/**
* 集合的最大長度 Integer.MAX_VALUE - 8 是為了減少出錯的機率 Integer 最大值已經很大了
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 增加容量,以確保它至少能容納最小容量引數指定的元素個數。
* @param 滿足條件的最小容量
*/
private void grow(int minCapacity) {
//獲取當前 elementData 的大小,也就是 List 中當前的容量
int oldCapacity = elementData.length;
//oldCapacity >> 1 等價於 oldCapacity / 2 所以新容量為當前容量的 1.5 倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果擴大1.5倍後仍舊比 minCapacity 小那麼直接等於 minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新陣列大小比 MAX_ARRAY_SIZE 就需要進一步比較 minCapacity 和 MAX_ARRAY_SIZE 的大小
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity通常接近 size 大小
//使用 Arrays.copyOf 構建一個長度為 newCapacity 新陣列 並將 elementData 指向新陣列
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
* 比較 minCapacity 與 Integer.MAX_VALUE - 8 的大小如果大則放棄-8的設定,設定為 Integer.MAX_VALUE
*/
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
擴容過程:
- 每次擴為原先大小的1.5倍.如果放不下,就以實際需要的空間大小為準.
- 將原來元素拷貝到一個擴容後陣列大小的長度新陣列中。所以 ArrayList 的擴容其實是相對來說比較消耗效能的。
末尾新增
先呼叫 ensureCapacityInternal 來判斷是否需要進行陣列擴容,然後將元素新增到陣列末尾:elementData[size++] = e;
size加一
指定位置新增
先擴容檢查,然後呼叫System#arraycopy
方法拷貝資料,再新增新元素.size加一
批量新增
刪除元素
根據下標刪除
主要是呼叫System#arraycopy
方法進行復制,然後將最後一位賦值為null(以便垃圾回收),size減一
刪除指定元素
/**
* 刪除指定元素,如果它存在則反會 true,如果不存在返回 false。
* 更準確地說是刪除集合中第一齣現 o 元素位置的元素 ,
* 也就是說只會刪除一個,並且如果有重複的話,只會刪除第一個次出現的位置。
*/
public boolean remove(Object o) {
// 如果元素為空則只需判斷 == 也就是記憶體地址
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
//得到第一個等於 null 的元素角標並移除該元素 返回 ture
fastRemove(index);
return true;
}
} else {
// 如果元素不為空則需要用 equals 判斷。
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
//得到第一個等於 o 的元素角標並移除該元素 返回 ture
fastRemove(index);
return true;
}
}
return false;
}
//移除元素的邏輯和 remve(Index)一樣
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
}
說明:
- 根據元素刪除只會刪除匹配的第一次出現的元素,後面的重複的元素不刪除
- 元素為null和不為null判斷邏輯不一樣.不為null時使用
equal
判斷是不是同一個物件. - 刪除是使用
System.arraycopy
拷貝前移,然後最後一位置空,邏輯和remve(Index)
一樣
批量移除/保留
改查
修改某下標元素,查詢某下標元素,查詢元素的下標或者list是否包含某元素.都是與內部陣列有關的簡單操作.
遍歷
迭代器
public Iterator<E> iterator() {
return new Itr();
}
問題: 為什麼迭代器刪除是安全的?
int cursor; // 對照 hasNext 方法 cursor 應理解為下個呼叫 next 返回的元素 初始為 0
int lastRet = -1; // 上一個返回的角標
int expectedModCount = modCount;//初始化的時候將其賦值為當前集合中的運算元
@SuppressWarnings("unchecked")
public E next() {
// 驗證期望的運算元與當前集合中的運算元是否相同 如果不同將會丟擲異常
checkForComodification();
// 如果迭代器的索引已經大於集合中元素的個數則丟擲異常,這裡不丟擲角標越界
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
// 由於多執行緒的問題這裡再次判斷是否越界,如果有非同步執行緒修改了List(增刪)這裡就可能產生異常
if (i >= elementData.length)
throw new ConcurrentModificationException();
// cursor 移動
cursor = i + 1;
//最終返回 集合中對應位置的元素,並將 lastRet 賦值為已經訪問的元素的下標
return (E) elementData[lastRet = i];
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
checkForComodification
方法檢驗在迭代期間是否有其他執行緒對元素做了改動.
// 實質呼叫了集合的 remove 方法移除元素
public void remove() {
// 比如操作者沒有呼叫 next 方法就呼叫了 remove 操作,lastRet 等於 -1的時候拋異常
if (lastRet < 0)
throw new IllegalStateException();
//檢查運算元
checkForComodification();
try {
//移除上次呼叫 next 訪問的元素
ArrayList.this.remove(lastRet);
// 集合中少了一個元素,所以 cursor 向前移動一個位置(呼叫 next 時候 cursor = lastRet + 1)
cursor = lastRet;
//刪除元素後賦值-1,確保先前 remove 時候的判斷
lastRet = -1;
//修改運算元期望值, modCount 在呼叫集合的 remove 的時候被修改過了。
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
// 集合的 remove 會有可能丟擲 rangeCheck 異常,catch 掉統一丟擲 ConcurrentModificationException
throw new ConcurrentModificationException();
}
}
注意:
-
1.迭代器的
remove
方法,內部也是呼叫ArrayList#remove
處理的. -
2.刪除完後,遊標cursor回退一個位置
-
3.遊標的remove()操作,一次迴圈只能呼叫一次
-
4.呼叫remove後modCod+1, expectedModCount 要設定為和modCod相等
-
5.lastRet重置為-1
根據第2點可知,迭代器的刪除方法,會在迭代器物件內維護一個遊標,該遊標是動態變化的.只要迭代器遍歷期間沒有執行緒改動list,在迴圈中進行操作都沒有問題.
而對list使用for迴圈,並在迴圈中使用ArrayList#remove
方法,就會有問題.第3點,可以根據程式碼看出:
if (lastRet < 0)
throw new IllegalStateException();
next方法會將當前的遊標位置賦值給lastRet:
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];
}
ListIterator 迭代器
ListIterator 繼承了Itr,因此不但可以向前遍,還可以向後遍歷.
安全性 modCount與expectedModCount
ArrayList 並不是一個執行緒安全的集合。如果集合的增刪操作需要保證執行緒的安全性,可以考慮使用 CopyOnWriteArrayList
或者使用 collections.synchronizedList(List l)
函式返回一個執行緒安全的ArrayList類.(CopyOnWriteArrayList
使用的是可重入鎖,collections.synchronizedList(List l)
使用synchronized關鍵字)
雖然不是執行緒安全的集合.但是在對集合進行操作時.由於有modCount與expectedModCount對比校驗,能夠很快地判斷是否有其他執行緒對資料進行了修改.
各類增加和刪除元素的方法都會導致modCount加1.而在writeObject,forEach,removeIf,replaceAll,sort等方法,Iterator,SubList,ArrayListSeperator等內部類
中都會進行校驗.
相關文章
- 搞懂 Java ArrayList 原始碼Java原始碼
- Java——ArrayList原始碼解析Java原始碼
- 搞懂 Java HashMap 原始碼JavaHashMap原始碼
- 【Java集合】ArrayList原始碼分析Java原始碼
- JAVA集合:ArrayList原始碼分析Java原始碼
- 搞懂 Java LinkedList 原始碼Java原始碼
- 搞懂 Java LinkedHashMap 原始碼JavaHashMap原始碼
- Java集合之ArrayList原始碼解析Java原始碼
- java基礎:ArrayList — 原始碼分析Java原始碼
- Java 集合框架------ArrayList原始碼分析Java框架原始碼
- java集合原始碼分析(三):ArrayListJava原始碼
- Java容器ArrayList原始碼淺析Java原始碼
- Java1.8-ArrayList原始碼解析Java原始碼
- Java 集合框架 ArrayList 原始碼剖析Java框架原始碼
- Java集合原始碼剖析——ArrayList原始碼剖析Java原始碼
- 【Java集合原始碼剖析】ArrayList原始碼剖析Java原始碼
- Java 8 ArrayList 原始碼簡單分析Java原始碼
- Java原始碼篇之容器類——ArrayListJava原始碼
- Java類集框架 —— ArrayList原始碼分析Java框架原始碼
- Java集合原始碼學習(2)ArrayListJava原始碼
- JAVA ArrayList集合底層原始碼分析Java原始碼
- Java集合乾貨——ArrayList原始碼分析Java原始碼
- Java容器原始碼學習--ArrayList原始碼分析Java原始碼
- Java集合——ArrayListJava
- JAVA集合-ArrayListJava
- 【java】【集合】去除ArrayList中的元素、ArrayList巢狀ArrayListJava巢狀
- 死磕 java集合之ArrayList原始碼分析Java原始碼
- Java集合-ArrayList原始碼解析-JDK1.8Java原始碼JDK
- Java集合乾貨1——ArrayList原始碼分析Java原始碼
- Java容器類框架分析(1)ArrayList原始碼分析Java框架原始碼
- Java 8 中 ArrayList 的變化原始碼分析Java原始碼
- Java 集合之ArrayListJava
- Java集合之ArrayListJava
- 【Java集合】2 ArrayListJava
- Java集合(一) —— ArrayListJava
- [Java基礎]ArrayListJava
- Java 集合 ArrayList 原始碼分析(帶著問題看原始碼)Java原始碼
- Java-ArrayList & LinkedList的原始碼對比分析Java原始碼