一、簡介
ArrayList結構
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
1、AbstractList,實作了List, RandomAccess, Cloneable, java.io.Serializable這些接口。
2、ArrayList 繼承了AbstractList,實作了List。它是一個數組隊列,提供了相關的添加、删除、修改、周遊等功能。
3、ArrayList 實作了RandmoAccess接口,即提供了随機通路功能。RandmoAccess是java中用來被List實作,為List提供快速通路功能的。在ArrayList中,我們即可以通過元素的序号快速擷取元素對象;這就是快速随機通路。
4、ArrayList 實作了Cloneable接口,即覆寫了函數clone(),能被克隆。
5、ArrayList 實作java.io.Serializable接口,這意味着ArrayList支援序列化,能通過序列化去傳輸。
注意
和Vector不同,ArrayList中的操作不是線程安全的!是以,建議在單線程中才使用ArrayList,而在多線程中可以選擇Vector或者CopyOnWriteArrayList,或者使用Collections工具類的synchronizedList方法将其包裝
二 、源碼分析
1、ArrayList屬性
//序列号
private static final long serialVersionUID = 8683452581122892189L;
//預設初始容量
private static final int DEFAULT_CAPACITY = 10;
//一個空數組,當使用者指定ArrayList容量為0時,傳回該數組
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 一個空數組執行個體
* - 當使用者沒有指定 ArrayList 的容量時(即調用無參構造函數),傳回的是該數組==>剛建立一個 ArrayList 時,其内資料量為 0。
* - 當使用者第一次添加元素時,該數組将會擴容,變成預設容量為 10(DEFAULT_CAPACITY) 的一個數組===>通過 ensureCapacityInternal() 實作
* 它與 EMPTY_ELEMENTDATA 的差別就是:該數組是預設傳回的,而後者是在使用者指定容量為 0 時傳回
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//目前資料對象存放地方,目前對象不參與序列化
//這個關鍵字最主要的作用就是當序列化時,被transient修飾的内容将不會被序列 化
transient Object[] elementData; // non-private to simplify nested class access
//ArrayList實際存儲的資料數量
private int size;
//繼承于AbstractList
//集合數組修改次數的辨別
protected transient int modCount = 0;
size和elementData.length是不相同的
- size:是指目前集合中存在的元素個數
- elementData.length:是指目前集合指定的容量大小
例如:
如果我們new ArrayList()時,ArrayList隻是給我們預設的elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA,此時隻是空數組,
隻有第一次add時才預設數組的大小為DEFAULT_CAPACITY=10 ,此時elementData.length=10,而size=0
2、ArrayList構造函數
/**
* 建立一個初試容量的、空的ArrayList
* @param initialCapacity 初始容量
* @throws IllegalArgumentException 當初試容量值非法(小于0)時抛出
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 無參構造函數:
* - 建立一個 空的 ArrayList,此時其内數組緩沖區 elementData = {}, 長度為 0
* - 當元素第一次被加入時,擴容至預設容量 10
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 建立一個包含collection的ArrayList
* @param c 要放入 ArrayList 中的集合,其内元素将會全部添加到建立的 ArrayList 執行個體中
* @throws NullPointerException 當參數 c 為 null 時抛出異常
*/
public ArrayList(Collection<? extends E> c) {
//把集合傳化成Object[]數組
elementData = c.toArray();
//轉化後的數組長度賦給目前ArrayList的size,并判斷是否為0
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
//c.toArray可能不會傳回 Object[],可以檢視 java 官方編号為 6260652 的 bug
if (elementData.getClass() != Object[].class)
// 若 c.toArray() 傳回的數組類型不是 Object[],則利用 Arrays.copyOf(); 來構造一個大小為 size 的 Object[] 數組
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 替換空數組
this.elementData = EMPTY_ELEMENTDATA;
}
}
Arrays.copyOf方法
/*
複制指定的數組,截斷或填充空字元,以便複制具有指定的長度
- original:要複制的數組
- newLength:要傳回的數組的長度
*/
public static char[] copyOf(char[] original, int newLength) {
char[] copy = new char[newLength];
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
//指定複制數組的範圍
public static char[] copyOfRange(char[] original, int from, int to) {
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
char[] copy = new char[newLength];
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
return copy;
}
/*
original - 要複制的數組
newLength - 要傳回的副本的長度
newType - 要傳回的副本的類型
*/
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
//根據class的類型來決定是new 還是反射去構造一個泛型數組
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
//利用native函數,批量指派元素至新數組中。
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
System.arraycopy方法
/*
将指定源數組的數組,從指定位置,複制到指定數組的指定位置,要指派的數組元素數量等于length。
- src:源數組
- srcPos:源數組中的起始位置
- dest:目标數組
- destPos:目标數組的起始位置
- length:要指派的數組元素數量
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
3、方法分析
1、add(E e)方法
快速掌握的方法:debug一遍
1.1 如果沒有指定初始化容量,第一次調用add()方法,會初始化容量為10
如果一開始指定了初始化容量,假如為4,那麼隻有在添加第5個元素時,ensureExplicitCapacity方法裡的if語句裡的minCapacity-elementData.length > 0 才能成立,此時就會以原容量的1.5倍進行擴容。
1.2 如果超出數組容量,預設配置設定大小為原數組的1.5倍。如果配置設定的數組過大(超過Integer.MAX_VALUE - 8,一般都不會那麼大),則調用hugeCapacity靜态方法 ,如果minCapacity介于Integer.MAX_VALUE - 8到Integer.MAX_VALUE,則直接配置設定一個Integer.MAX_VALUE大小的數組,否則抛出OutOfMemoryError。
1、確定數組已使用長度(size)加1之後足夠存下 下一個資料
2、修改次數modCount 辨別自增1,如果目前數組已使用長度(size)加1後的大于目前的數組長度,則調用grow方法,增長數組,grow方法會将目前數組的長度變為原來容量的1.5倍。
3、確定新增的資料有地方存儲之後,則将新元素添加到位于size的位置上。
4、傳回添加成功布爾值。
/**
*增加指定的元素到ArrayList的最後位置
* @param e 要添加的元素
* @return
*/
public boolean add(E e) {
// 确定ArrayList的容量大小---嚴謹
// 注意:size + 1,保證資源空間不被浪費,
// ☆☆☆按目前情況,保證要存多少個元素,就隻配置設定多少空間資源
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/**
* 私有方法:明确 ArrayList 的容量,提供給本類使用的方法
* - 用于内部優化,保證空間資源不被浪費:尤其在 add() 方法添加時起效
* @param minCapacity 指定的最小容量
*/
private void ensureCapacityInternal(int minCapacity) {
// 若 elementData == {},則取 minCapacity 為 預設容量和參數 minCapacity 之間的最大值
// 注:ensureCapacity() 是提供給使用者使用的方法,在 ArrayList 的實作中并沒有使用
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity= Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
/**
* 私有方法:明确 ArrayList 的容量
* - 用于内部優化,保證空間資源不被浪費:尤其在 add() 方法添加時起效
* @param minCapacity 指定的最小容量
*/
private void ensureExplicitCapacity(int minCapacity) {
// 将“修改統計數”+1,該變量主要是用來實作fail-fast機制的
modCount++;
// 防止溢出代碼:確定指定的最小容量 > 數組緩沖區目前的長度
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* 數組緩沖區最大存儲容量
* - 一些 VM 會在一個數組中存儲某些資料--->為什麼要減去 8 的原因
* - 嘗試配置設定這個最大存儲容量,可能會導緻 OutOfMemoryError(當該值 > VM 的限制時)
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 私有方法:擴容,以確定 ArrayList 至少能存儲 minCapacity 個元素
* - 擴容計算:newCapacity = oldCapacity + (oldCapacity >> 1); 擴充目前容量的1.5倍
* @param minCapacity 指定的最小容量
*/
private void grow(int minCapacity) {
// 防止溢出代碼
int oldCapacity = elementData.length;
// 運算符 >> 是帶符号右移. 如 oldCapacity = 10,則 newCapacity = 10 + (10 >> 1) = 10 + 5 = 15
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0) // 若 newCapacity 依舊小于 minCapacity
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) // 若 newCapacity 大于最大存儲容量,則進行大容量配置設定
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
* 私有方法:大容量配置設定,最大配置設定 Integer.MAX_VALUE
* @param minCapacity
*/
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
>> 算術右移
void add(int index, E element)
1、檢查數組下标是否越界,越界抛出異常
2、確定數組已使用長度(size)加1之後足夠存下 下一個資料
3、修改次數modCount 辨別自增1,如果目前數組已使用長度(size)加1後的大于目前的數組長度,則調用grow方法,增長數組,grow方法會将目前數組的長度變為原來容量的1.5倍。
4、使用System.arraycopy把index後面的元素都往後移動一位,騰出index位置
5、将新的資料放到指定位置index上
public void add(int index, E element) {
//判斷索引是否越界
rangeCheckForAdd(index);
//確定已使用數組長度size+1能夠存下資料
ensureCapacityInternal(size + 1); // Increments modCount!!
//elementData:要複制的數組,index從哪裡開始複制
//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 String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
2、remove(int index)方法
1、判斷索引是否越界
2、增加修改的次數
3、儲存要删除的元素為oldValue
4、将指定位置index+1往後的元素都向前移動一位,覆寫需要删除的元素
5、将最後一個元素置空,GC
6、傳回删除的元素
/**
* 移除指定位置的元素
* index 之後的所有元素依次左移一位
* @param index 指定位置
* @return 被移除的元素
* @throws IndexOutOfBoundsException
*/
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;//要移動的長度
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将最後一個元素置空
elementData[--size] = null;
return oldValue;
}
/**
* 檢查數組是否在界線内
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
remove(Object o)
/**
* 移除list中指定的第一個元素(符合條件索引最低的)
* 如果list中不包含這個元素,這個list不會改變
* 如果包含這個元素,index 之後的所有元素依次左移一位
* @param o 這個list中要被移除的元素
* @return
*/
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
/**
* 快速删除第 index 個元素
* 和public E remove(int index)相比
* 私有方法,跳過檢查,不傳回被删除的值
* @param index 要删除的腳标
*/
private void fastRemove(int index) {
modCount++;//這個地方改變了modCount的值了
int numMoved = size - index - 1;//移動的個數
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; //将最後一個元素清除
}
3、iterator疊代器
疊代器統一了對容器的通路方式,能夠将周遊序列的操作與序列底層的結構分離。
1、使用方法iterator()要求容器傳回一個Iterator。Iterator将準備号傳回序列的第一個元素
2、使用next()獲得序列中的下一個元素
3、使用hasNext()檢查序列是否還有元素
4、使用remove()将疊代器新近傳回的元素删除
/**
* 以一種合适的排序傳回一個iterator到元素的結尾
*/
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int cursor; // 下一個元素傳回的索引
int lastRet = -1; // 最後一個元素傳回的索引 -1 if no such
int expectedModCount = modCount;
/**
* 是否有下一個元素
*/
public boolean hasNext() {
return cursor != size;
}
/**
* 傳回list中的值
*/
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;//i目前元素的索引
if (i >= size)//第一次檢查:角标是否越界越界
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)//第二次檢查,list集合中數量是否發生變化
throw new ConcurrentModificationException();
cursor = i + 1; //cursor 下一個元素的索引
return (E) elementData[lastRet = i];//最後一個元素傳回的索引
}
/**
* 移除集合中的元素
*/
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
//移除list中的元素
ArrayList.this.remove(lastRet);
//由于cursor比lastRet大1,所有這行代碼是指指針往回移動一位
cursor = lastRet;
//将最後一個元素傳回的索引重置為-1
lastRet = -1;
//重新設定了expectedModCount的值,避免了ConcurrentModificationException的産生
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
/**
* 檢查modCount是否等于expectedModCount
* 在 疊代時list集合的元素數量發生變化時會造成這兩個值不相等
*/
final void checkForComodification() {
//當expectedModCount和modCount不相等時,就抛出ConcurrentModificationException
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
并發修改異常
深入分析for-each和疊代器
1、foreach可以操作數組: 底層依然采用for循環+ 索引來擷取數組元素.
2、foreach可以操作Iterable的執行個體:底層其實采用的Iterator(疊代器).
為什麼會報ConcurrentModificationException異常?
1、Iterator 是工作在一個獨立的線程中,并且擁有一個 mutex 鎖。
2、 Iterator 被建立之後會建立一個指向原來對象的單鍊索引表,當原來的對象數量發生變化時,
這個索引表的内容不會同步改變,是以當索引指針往後移動的時候就找不到要疊代的對象。
3、是以按照 fail-fast 原則 Iterator 會馬上抛出 java.util.ConcurrentModificationException 異常。
4、是以 Iterator 在工作的時候是不允許被疊代的對象被改變的。但你可以使用 Iterator 本身的方法 remove() 來删除對象,
5、Iterator.remove() 方法會在删除目前疊代對象的同時維護索引的一緻性。
在Collection接口中存在删除指定元素的方法:boolean remove(Object ele),該方法隻能從集合中删除元素,不能把疊代器中指定的元素也删除。
如何解決并發修改異常呢?
不要使用集合對象的删除方法,使用Iterator中的remove方法,方法會從兩個線程中同時移除被删除的元素,.保證了兩個線程的同步.
并發修改問題
ListItr,可以雙向疊代
/**
* AbstractList.ListItr 的優化版本
* ListIterator 與普通的 Iterator 的差別:
* - 它可以進行雙向移動,而普通的疊代器隻能單向移動
* - 它可以添加元素(有 add() 方法),而後者不行
*/
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
cursor = index;
}
/**
* 是否有前一個元素
*/
public boolean hasPrevious() {
return cursor != 0;
}
/**
* 擷取下一個元素的索引
*/
public int nextIndex() {
return cursor;
}
/**
* 擷取 cursor 前一個元素的索引
* - 是 cursor 前一個,而不是目前元素前一個的索引。
* - 若調用 next() 後馬上調用該方法,則傳回的是目前元素的索引。
* - 若調用 next() 後想擷取目前元素前一個元素的索引,需要連續調用兩次該方法。
*/
public int previousIndex() {
return cursor - 1;
}
/**
* 傳回 cursor 前一進制素
*/
@SuppressWarnings("unchecked")
public E previous() {
checkForComodification();
int i = cursor - 1;
if (i < 0)//第一次檢查:索引是否越界
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)//第二次檢查
throw new ConcurrentModificationException();
cursor = i;//cursor回移
return (E) elementData[lastRet = i];//傳回 cursor 前一進制素
}
/**
* 将數組的最後一個元素,設定成元素e
*/
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
//将數組最後一個元素,設定成元素e
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
/**
* 添加元素
*/
public void add(E e) {
checkForComodification();
try {
int i = cursor;//目前元素的索引後移一位
ArrayList.this.add(i, e);//在i位置上添加元素e
cursor = i + 1;//cursor後移一位
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
4 、其他部分方法
/**
* 将數組緩沖區大小調整到實際 ArrayList 存儲元素的大小,即 elementData = Arrays.copyOf(elementData, size);
* - 該方法由使用者手動調用,以減少空間資源浪費的目的 ce.
*/
public void trimToSize() {
// modCount 是 AbstractList 的屬性值:protected transient int modCount = 0;
// [問] modCount 有什麼用?
modCount++;
// 當實際大小 < 數組緩沖區大小時
// 如調用預設構造函數後,剛添加一個元素,此時 elementData.length = 10,而 size = 1
// 通過這一步,可以使得空間得到有效利用,而不會出現資源浪費的情況
if (size < elementData.length) {
// 注意這裡:這裡的執行順序不是 (elementData = (size == 0) ) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
// 而是:elementData = ((size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size));
// 這裡是運算符優先級的文法
// 調整數組緩沖區 elementData,變為實際存儲大小 Arrays.copyOf(elementData, size)
//先判斷size是否為0,如果為0:實際存儲為EMPTY_ELEMENTDATA,如果有資料就是Arrays.copyOf(elementData, size)
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
/**
* 指定 ArrayList 的容量
* @param minCapacity 指定的最小容量
*/
public void ensureCapacity(int minCapacity) {
// 最小擴充容量,預設是 10
//這句就是:判斷是不是空的ArrayList,如果是的最小擴充容量10,否則最小擴充量為0
//上面無參構造函數建立後,當元素第一次被加入時,擴容至預設容量 10,就是靠這句代碼
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
? 0
: DEFAULT_CAPACITY;
// 若使用者指定的最小容量 > 最小擴充容量,則以使用者指定的為準,否則還是 10
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
/**
* 移除list中的所有元素,這個list表将在調用之後置空
* - 它會将數組緩沖區是以元素置為 null
* - 清空後,我們直接列印 list,卻隻會看見一個 [], 而不是 [null, null, ….] ==> toString() 和 疊代器進行了處理
*/
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
參考:https://www.cnblogs.com/gxl1995/p/7534171344218b3784f1beb90d621337.html(合并詳細)
參考:https://blog.csdn.net/fighterandknight/article/details/61240861(分開詳細)