[直擊native] 單步調試ArrayList 源碼 二
上一節講到iterator() , iterator() 方法涉及到ArrayList的內部類Itr , 這一節重點講解 這個迭代器 ltr
將之前先放一段問題代碼
@Test
public void preLoopTest(){
List<String> arr = new ArrayList<>();
arr.add("1");
arr.add("4");
Iterator<String> iterator = arr.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
if ("4".equals(next)) {
iterator.remove();
}
}
System.out.println(Arrays.toString(arr.toArray()));
List<String> arr1 = new ArrayList<>();
arr1.add("1");
arr1.add("4");
for (String a : arr1) {//java.util.ConcurrentModificationException
if ("4".equals(a)) {
arr1.remove(a);
}
}
System.out.println(Arrays.toString(arr1.toArray()));
}
如果反編譯過源碼的同學應該知道
for (String a : arr1)
反編譯過來for 循環 也是 使用了迭代器 , 那麼問題出在哪裏?我們從源碼中找問題所在 .
找到異常(java.util.ConcurrentModificationException)代碼
@SuppressWarnings("unchecked")
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];
}
final void checkForComodification() {
if (modCount != expectedModCount)//判斷modCount 修改次數不等於 expectedModCount
throw new ConcurrentModificationException();
}
那麼這個expectedModCount 是代表什麼?
看內部迭代器源碼
快速失敗(fail—fast)
在用迭代器遍歷一個集合對象時,如果遍歷過程中對集合對象的內容進行了修改(增加、刪除、修改),則會拋出Concurrent Modification Exception。
原理:迭代器在遍歷時直接訪問集合中的內容,並且在遍歷過程中使用一個 modCount 變量。集合在被遍歷期間如果內容發生變化,就會改變modCount的值。每當迭代器使用hashNext()/next()遍歷下一個元素之前,都會檢測modCount變量是否爲expectedmodCount值,是的話就返回遍歷;否則拋出異常,終止遍歷。
注意:這裏異常的拋出條件是檢測到 modCount!=expectedmodCount 這個條件。如果集合發生變化時修改modCount值剛好又設置爲了expectedmodCount值,則異常不會拋出。因此,不能依賴於這個異常是否拋出而進行併發操作的編程,這個異常只建議用於檢測併發修改的bug。
場景:java.util包下的集合類都是快速失敗的,不能在多線程下發生併發修改(迭代過程中被修改)。
所以在 new ltr 對象時傳出對象包含了修改次數,這是爲併發做的檢測操作 , 但是在循環遍歷過程中修改元素也可以看做另類的併發.
此時我們已經知道了原因,但是爲什麼直接使用迭代器刪除不會出現異常?
下面繼續分析一下源碼流程
/**
按正確的順序返回列表中元素的迭代器。
* Returns an iterator over the elements in this list in proper sequence.
* 這個返回的迭代器是快速失敗(fail—fast)
* <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
*
* @return an iterator over the elements in this list in proper sequence
*/
public Iterator<E> iterator() {
// 返回內部迭代器
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
// 下一個返回元素的索引
int cursor; // index of next element to return
// 上一個已經返回的元素索引
int lastRet = -1; // index of last element returned; -1 if no such
// 預期修改次數
int expectedModCount = modCount;
// 是否有下一個元素
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
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];
}
// 移除元素
public void remove() {
// 如果沒有查出元素 那麼也不存在刪除 所以返回了一個非法狀態異常
if (lastRet < 0)
throw new IllegalStateException();
// 同樣要檢測一下
checkForComodification();
try {
// 調用了 ArrayList 的remove 方法 將之前,調用next返回的對象刪除
ArrayList.this.remove(lastRet);
// 可以回憶下remove 方法中有數組拷貝的函數將刪除元素右邊的全部左移一位,所以這裏將指針指向左移一位
cursor = lastRet;
// 再將上一個置爲-1
lastRet = -1;
// 關鍵操作: 刪除之後再此對modCount同步到了預期修改值expectedModCount
// 所以再次調用next 值時 不會出現異常 , 並且使用這個函數 同期修改了cursor
// 所以在hasNext 便會體現真正大小
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
// 這個函數是在1.8加入的新方法 , 用於增強遍歷 , 可以結合lambda 表達式遍歷數據
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
// 傳入參數 接口實現類 不爲空
Objects.requireNonNull(consumer);
// 數組大小
final int size = ArrayList.this.size;
// 當前遊標
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
// 邊界判斷
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
// 對數組每個對象執行傳入的固定的接口方法
// 遍歷爲當前遊標到末尾 並且是修改次數沒變化的前提下
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// 更新最後的遊標 和最後一次之前的遊標位置
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
// 最後再此檢測是否修改
// 所以如果在傳入參數中的方法中修改也會出現併發修改異常
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
最後總結
如果是需要遍歷時候修改集合內元素,可以使用set 方法
如果需要刪除集合內的元素可以使用迭代器的remove 方法
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
### 最後總結
如果是需要遍歷時候修改集合內元素,可以使用set 方法
如果需要刪除集合內的元素可以使用迭代器的remove 方法