Java基礎List詳解
目錄(本文基於JDK1.8)
1. 前言
我們在日常程式設計中,常常需要集中存放多個數據。這時候,陣列是我們一個很好的選擇,但是陣列在初始化的時候指定了長度之後,這個陣列長度就是不可變的,所以使用陣列的話我們需要實現知道需要儲存物件的數量。如果我們需要儲存一個可以動態增長的資料,這個時候我們就需要集合類,這裡我們就來學習一下集合類中的List。
2. List基本概念
List繼承自Collection介面,用來儲存一組相同型別的元素,List集合代表一個元素有序、可重複的集合,它和Set的區別是:元素有放入順序,元素可重複 。有順序,即先放入的元素排在前面。
List
主要有ArrayList
、LinkedList
、Vector
幾種實現方式,這三者都實現了List介面。關於它們我們在,Java集合類裡面有做簡單的介紹。
2.1 ArrayList部分原始碼詳解
2.1.1 ArrayList類的定義
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- ArrayList繼承AbstractList抽象父類
- 實現List介面規定了List的操作規範。
- 實現RandomAccess(標記介面)代表ArrayList是支援快速隨機訪問。
- 實現Cloneable(標記介面)代表ArrayList是可以拷貝。
- 實現Serializable(標記介面)代表ArrayList是可以序列化的。
2.1.2 ArrayList欄位屬性
/**
* 預設初始容量.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 用於空例項的共享空陣列例項(當用戶指定該 ArrayList 容量為 0 時,返回該空陣列).
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 預設大小空例項的共享空陣列例項
* 當用戶使用無參建構函式建立ArrayList例項的時候,返回的是該陣列。
* 當用戶第一次新增元素的時候,該陣列將會擴容,變成容量為預設初始容量的一個數組。
* 它與 EMPTY_ELEMENTDATA的區別就是:該陣列是不指定容量返回的,而後者是在使用者指 定容量為 0 時返回。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* ArrayList的容量是這個陣列緩衝區的長度
* 任何空陣列(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
* 在新增第一個元素的時候將會把容量擴充到DEFAULT_CAPACITY即10
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* ArrayList的大小(它包含的元素的數量).
*
* @serial
*/
private int size;
2.1.3 ArrayList的構造方法
/**
*
* 用指定的初始容量構造一個空的ArrayList.
*
* @param initialCapacity ArrayList的初始容量
* @throws 如果指定的初始容量的值為負值則丟擲 IllegalArgumentException
*
*/
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;
}
/**
* 構造一個包含指定元素的列表的列表集合,按照集合的順序返回迭代器
*
* @param c 要放入 ArrayList 中的集合,其內元素將會全部新增到新建的 ArrayList 例項中
* @throws 如果指定的集合為null則丟擲 NullPointerException
*/
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;
}
}
因為ArrayList的初始容量比較小,所以如果能預估資料量的話,分配一個較大的初始值屬於最佳實踐,這樣可以減少調整大小的開銷。
2.1.4 其他方法
/**
* 將這個ArrayList例項的容量變成ArrayList當前的大小
*
* 因為容量常常會大於實際元素的數量。記憶體緊張時,可以呼叫該方法刪除預留的位置,調整容量為元素實際數量。
* 如果確定不會再有元素新增進來時也可以呼叫該方法來節約空間。
*/
public void trimToSize() {
//modCount是在其父類AbstractList中定義的,指list結構變化的次數,其中
//結構變化指list的大小,即list包含元素的個數的變化
modCount++;
//先判斷ArrayList裡面的元素數量是不是小於容量
if (size < elementData.length) {
//如果size < 容量,判斷size是不是等於0,如果等於0,容量為EMPTY_ELEMENTDATA
//如果size不為0,容量為Arrays.copyOf(elementData, size)。
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
/**
* 使用指定引數設定ArrayList容量
*
* @param minCapacity 所需最小容量
*/
public void ensureCapacity(int minCapacity) {
//如果陣列為空,容量預取0,否則取預設的初始容量10。
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
//如果指定容量大於預設容量,則用指定容量設定陣列容量
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
/**
* 得到最小擴容量
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果elementData為空陣列的時候,則返回初始容量和minCapacity其中大的一個
//不為空則返回 minCapacity
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
/**
* 擴容
*/
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
/**
* 私有方法:明確 ArrayList 的容量
* -用於內部優化,保證空間資源不被浪費:在 add() 方法新增時起效
* @param minCapacity 指定的最小容量
*/
private void ensureExplicitCapacity(int minCapacity) {
//// 將“list結構變化的次數”+1,該變數主要是用來實現fail-fast機制的
modCount++;
// 防止溢位程式碼:確保指定的最小容量 > 陣列緩衝區當前的長度
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* 陣列最大容量,如果視分配更大可能會導致OOM
*
* 2^31 = 2,147,483,648,陣列本身要佔用 8 bytes儲存大小,所以 2^31 -8
*/
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) {
// overflow-conscious code
// 獲取到ArrayList中elementData陣列的記憶體空間長度
int oldCapacity = elementData.length;
//右移一位等於自身一半,所以擴容到原來的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 若 newCapacity 依舊小於 minCapacity,則用minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 若 newCapacity 大於最大儲存容量,則返回最大整數值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
// 呼叫Arrays.copyOf方法將elementData陣列指向新的記憶體空間時newCapacity的連續空間
// 並將elementData的資料複製到新的記憶體空間
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
* 檢查是否溢位,若沒有溢位,返回最大整數值(java中的int為4位元組,所以最大為0x7fffffff)或預設最大值.
*/
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實際儲存的元素數量.
*/
public int size() {
return size;
}
/**
* ArrayList是否有元素
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 判斷是否包含一個元素(根據 indexOf()判斷)
*/
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
/**
* 返回一個值在陣列首次出現的位置,會根據是否為null使用不同方式判斷。不存在就返回-1。時間複雜度為O(N)
*/
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;
}
/**
* 返回一個值在陣列最後一次出現的位置,會根據是否為null使用不同方式判斷。不存在就返回-1。時間複雜度為O(N)
*/
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
/**
* 返回這個ArrayList例項的一個淺副本。(元素本身不會被複制。)
*/
public Object clone() {
try {
// 呼叫父類(翻看原始碼可見是Object類)的clone方法得到一個ArrayList副本
ArrayList<?> v = (ArrayList<?>) super.clone();
// 呼叫Arrays類的copyOf,將ArrayList的elementData陣列賦值給副本的elementData陣列
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
// 返回副本v
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
/**
* 轉換為Object陣列,使用Arrays.copyOf()方法.
*
* @return 一個數組包含所有列表中的元素, 且順序正確
*/
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
/**
* 如果a的長度小於ArrayList的長度,直接呼叫Arrays類的copyOf,返回一個比a陣列長度要大的新陣列,裡面元素就是ArrayList裡面的元素;
*
*如果a的長度比ArrayList的長度大,那麼就呼叫System.arraycopy,將ArrayList的elementData陣列賦值到a陣列,然後把a陣列的size位置賦值為空
*
* @param a 如果它的長度大的話,列表元素將儲存在這個陣列中; 否則,將為此分配一個相同執行時型別的新陣列。
* @return 一個包含ArrayList元素的陣列
* @throws 將與陣列型別不相容的值賦值給陣列元素時丟擲的異常ArrayStoreException
* @throws 如果指定的陣列為空則丟擲NullPointerException
*/
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
// 若陣列a的大小 < ArrayList的元素個數,則新建一個T[]陣列,
// 陣列大小是"ArrayList的元素個數",並將“ArrayList”全部拷貝到新陣列中
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
// 若陣列a的大小 >= ArrayList的元素個數,則將ArrayList的全部元素都拷貝到陣列a中。
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
/**
* 返回指定位置的值
*
* @param index
* @return
*/
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
/**
* 返回ArrayList指定位置的元素.但是會先檢查這個位置數否超出陣列長度
*
* @param index 返回元素的索引
* @return 在這個ArrayList中指定位置的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
// 檢查是否越界
rangeCheck(index);
// 返回ArrayList的elementData陣列index位置的元素
return elementData(index);
}
/**
* 設定指定位置為一個新值,並返回之前的值,會檢查這個位置是否超出陣列長度.
*
* @param index 要替換的元素的索引
* @param element 現在要儲存在指定位置的元素
* @return 之前在指定位置的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
// 檢查是否越界
rangeCheck(index);
// 呼叫elementData(index)獲取到當前位置的值
E oldValue = elementData(index);
// 將element賦值到ArrayList的elementData陣列的第index位置
elementData[index] = element;
return oldValue;
}
/**
* 增加指定的元素到ArrayList的最後位置
*
* @param e 要新增的元素
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//確定ArrayList的容量大小
//若list是第一次新增元素則,初始容量為預設的10
//若不是第一次新增元素,則先擴容自身的1.5倍容量和初始容量比較
//如果比初始容量小則取初始容量,如果比初始容量大則返回最大整數值
ensureCapacityInternal(size + 1); // Increments modCount!!
// 將e賦值給elementData的size+1的位置
elementData[size++] = e;
return true;
}
/**
* 在這個ArrayList中的指定位置插入指定的元素,.
*
* @param index 指定元素將被插入的索引
* @param element 要插入的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
// 判斷index是否越界
rangeCheckForAdd(index);
//確定ArrayList的容量大小
ensureCapacityInternal(size + 1); // Increments modCount!!
//src:源陣列; srcPos:源陣列要複製的起始位置; dest:目的陣列; destPos:目的陣列放置的起始位置; length:複製的長度
// 將elementData從index位置開始,複製到elementData的index+1開始的連續空間
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 在elementData的index位置賦值element
elementData[index] = element;
// ArrayList的大小加一
size++;
}
/**
*在ArrayList的移除index位置的元素,會檢查新增的位置,返回之前的值
*
* @param index 要刪除的元素的索引
* @return 從ArrayList中刪除的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
// 判斷是否越界
rangeCheck(index);
modCount++;
//獲取要刪除元素的索引上的值
E oldValue = elementData(index);
//獲取ArrayList內元素要移動的長度
int numMoved = size - index - 1;
if (numMoved > 0)
// 將elementData陣列index+1位置開始拷貝到elementData從index開始的空間
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 使size-1 ,設定elementData的最後一個位置為空,讓GC來清理記憶體空間
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
/**
* 在ArrayList的移除物件為O的元素,跟indexOf方法思想基本一致
*
* @param o 要從該列表中刪除的元素(如果存在)
* @return true 如果這個列表包含指定的元素
*/
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;
}
/*
* 快速刪除指定位置的值,之所以叫快速,應該是不需要檢查和返回值,因為只內部使用
*/
private void fastRemove(int index) {
modCount++;
//獲取ArrayList內元素要移動的長度
int numMoved = size - index - 1;
if (numMoved > 0)
// 將elementData陣列index+1位置開始拷貝到elementData從index開始的空間
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 使size-1 ,設定elementData的最後一個位置為空,讓GC來清理記憶體空間
elementData[--size] = null; // clear to let GC do its work
}
/**
* 清空陣列,把每一個值設為null,方便垃圾回收(不同於reset,陣列預設大小有改變的話不會重置)
*/
public void clear() {
//這個地方改變了modCount的值了
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
//便於垃圾回收器回收
elementData[i] = null;
//把size設定為0,以便我們不會瀏覽到null值的記憶體空間
size = 0;
}
/**
* 新增一個集合的元素到末端,若要新增的集合為空返回false
*
* @param c 包含要新增到此列表中的元素的集合
* @return list 元素個數有改變時,成功:失敗
* @throws 如果指定的集合為null 則丟擲NullPointerException
*/
public boolean addAll(Collection<? extends E> c) {
// 將c轉換為陣列a
Object[] a = c.toArray();
// 獲取a佔的記憶體空間長度賦值給numNew
int numNew = a.length;
//擴容原ArrayList的容量為size + numNew
ensureCapacityInternal(size + numNew); // Increments modCount
// 將a的第0位開始拷貝至elementData的size位開始,拷貝長度為numNew
System.arraycopy(a, 0, elementData, size, numNew);
//將size的長度增加numNew
size += numNew;
// 如果c為空,返回false,c不為空,返回true
return numNew != 0;
}
/**
* 從第index位開始,將c全部拷貝到ArrayList,若要新增的集合為空返回false
*
* @param index 在哪個索引處插入指定集合中的第一個元素
* @param c 包含要新增到此列表中的元素的集合
* @return list 元素個數有改變時,成功:失敗
* @throws IndexOutOfBoundsException {@inheritDoc}
* @throws 如果指定的集合是空的則丟擲 NullPointerException
*/
public boolean addAll(int index, Collection<? extends E> c) {
// 判斷index大於size或者是小於0,如果是,則丟擲IndexOutOfBoundsException異常
rangeCheckForAdd(index);
// 將c轉換為陣列a
Object[] a = c.toArray();
int numNew = a.length;
//擴容原ArrayList的容量為size + numNew
ensureCapacityInternal(size + numNew); // Increments modCount
//獲取ArrayList內元素要移動的長度
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
/**
* 刪除指定範圍元素。引數為開始刪的位置和結束位置
*
* @throws IndexOutOfBoundsException if {@code fromIndex} or
* {@code toIndex} is out of range
* ({@code fromIndex < 0 ||
* fromIndex >= size() ||
* toIndex > size() ||
* toIndex < fromIndex})
*/
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
//獲取ArrayList內元素要移動的長度
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
//便於垃圾回收期回收
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
/**
* 檢查index是否超出陣列長度 用於新增元素時
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* 判斷index大於size或者是小於0,如果是,則丟擲IndexOutOfBoundsException異常
*/
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;
}
/**
* RArrayList移除集合c中的所有元素
*/
public boolean removeAll(Collection<?> c) {
// 如果c為空,則丟擲空指標異常
Objects.requireNonNull(c);
return batchRemove(c, false);
}
/**
* 僅保留指定集合c中的元素
*/
public boolean retainAll(Collection<?> c) {
// 如果c為空,則丟擲空指標異常
Objects.requireNonNull(c);
return batchRemove(c, true);
}
/**
* 根據complement值,將ArrayList中包含c中元素的元素刪除或者保留
*
* @param c
* @param complement true時從陣列保留指定集合中元素的值,為false時從陣列刪除指定集合中元素的值。
* @return 陣列中重複的元素都會被刪除(而不是僅刪除一次或幾次),有任何刪除操作都會返回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++)
// 如果c中不包含elementData[r]這個元素
if (c.contains(elementData[r]) == complement)
// 則直接將r位置的元素賦值給w位置的元素,w自增
elementData[w++] = elementData[r];
} finally {
// 防止丟擲異常導致上面r的右移過程沒完成
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
// 將r未右移完成的位置的元素賦值給w右邊位置的元素
System.arraycopy(elementData, r,
elementData, w,
size - r);
// 修改w值增加size-r
w += size - r;
}
// 如果有被覆蓋掉的元素,則將w後面的元素都賦值為null
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;
}
/**
* 儲存陣列例項的狀態到一個流(即序列化)。寫入過程陣列被更改會丟擲異常
*
* @serialData The length of the array backing the <tt>ArrayList</tt>
* instance is emitted (int), followed by all of its elements
* (each an <tt>Object</tt>) in the proper order.
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
//執行預設的反序列化/序列化過程。將當前類的非靜態和非瞬態欄位寫入此流
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
// 寫入大小
s.writeInt(size);
// Write out all elements in the proper order.
// 按順序寫入所有元素
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
/**
* 從流中重構ArrayList例項(即反序列化)
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
// 執行預設的序列化/反序列化過程
s.defaultReadObject();
// Read in capacity
// 讀入陣列長度
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
// 像clone()方法 ,但根據大小而不是容量分配陣列
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
//讀入所有元素
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
/**
* 返回一個從index開始的ListIterator物件
*
* <p>The returned list iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public ListIterator<E> listIterator(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}
/**
* 返回一個ListIterator物件,ListItr為ArrayList的一個內部類,其實現了ListIterator<E> 介面口
*
* <p>The returned list iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
*
* @see #listIterator(int)
*/
public ListIterator<E> listIterator() {
return new ListItr(0);
}
/**
* 返回一個Iterator物件,Itr為ArrayList的一個內部類,其實現了Iterator<E>介面
*
* @return an iterator over the elements in this list in proper sequence
*/
public Iterator<E> iterator() {
return new Itr();
}
2.2 什麼是fail-fast,什麼是fail-safe
上面原始碼裡面我們提到了modcount++ 是為了實現fail-fast,現在我們看一下什麼是 fail-fast。
2.2.1 fail-fast (快速失敗)
在用迭代器遍歷一個集合物件時,如果遍歷過程中對集合物件的內容進行了修改(增加、刪除、修改),則會丟擲ConcurrentModificationException
fail-fast
會在以下兩種情況下丟擲ConcurrentModificationException
1、單執行緒環境 集合被建立後,在遍歷它的過程中修改了結構。
2、多執行緒環境 當一個執行緒在遍歷這個集合,而另一個執行緒對這個集合的結構進行了修改。
2.2.1.1 fail-fast機制是如何檢測的?
迭代器在遍歷過程中是直接訪問內部資料的,因此內部的資料在遍歷的過程中無法被修改。為了保證不被修改,迭代器內部維護了一個
modCount
變數 ,當集合結構改變(新增刪除或者修改),modCount
會被修改,而迭代器每次的hasNext()
和next()
方法都會檢查modCount
t變數是否為expectedmodCount
,如果是的話就返回遍歷,如果檢測到被修改時,丟擲Concurrent Modification Exception
。
注意:java.util包下的集合類都是快速失敗的,不能在多執行緒下發生併發修改(迭代過程中被修改)。
2.2.2 fail—safe (安全失敗)
採用安全失敗機制的集合容器,在遍歷時不是直接在集合內容上訪問的,而是先複製原有集合內容,在拷貝的集合上進行遍歷,因此不會丟擲
ConcurrentModificationException
2.2.2.1 fail—safe的原理
由於迭代時是對原集合的拷貝進行遍歷,所以在遍歷過程中對原集合所作的修改並不能被迭代器檢測到,所以不會觸發
ConcurrentModificationException
。
2.2.2.1 fail—safe的缺點
- 1、因為迭代器遍歷的是開始遍歷那一刻拿到的集合拷貝,在遍歷期間原集合發生的修改迭代器是不知道的,也就是說無法保證讀取的資料是目前原始資料結構中的資料。
- 2、需要複製集合,產生大量的無效物件,開銷大
注意:java.util.concurrent包下的容器都是安全失敗,可以在多執行緒下併發使用,併發修改。
2.2.3 如何在遍歷的同時刪除ArrayList中的元素
Java中遍歷集合的方法有好幾種,例如普通for迴圈、增強for迴圈、迭代器以及lambda表示式的forecah,但是通過上面提到的fail-fast機制,我們知道遍歷過程修改資料是會觸發fail-fast的,那麼如何如何在遍歷的同時刪除ArrayList中的元素呢?這裡推薦使用迭代器遍歷。
Iterator<People> peo = peoples.iterator();
while (peo.hasNext()) {
People people = peo.next();
if (people.getId() == 2)
people.remove();//這裡要使用Iterator的remove方法移除當前物件,如果使用List的remove方法,則同樣會出現ConcurrentModificationException
}
2.3 LinkedList部分原始碼詳解
2.3.1 LinkedList類的定義
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
- LinkedList繼承了AbstractSequentialList,它可以被當作堆疊、佇列或雙端佇列進行操作。
- 實現List介面規定了List的操作規範。
- 實現 Deque 介面代表LinkedList可以當作雙端佇列使用。
- 實現Cloneable(標記介面)代表LinkedList是可以拷貝。
- 實現Serializable(標記介面)代表LinkedList是可以序列化的。