1、背景 html
在之前的隨筆中說道過ArrayList的foreach迭代刪除的問題:ArrayList迭代過程刪除問題java
按照之前的說法,在ArrayList中經過foreach迭代刪除會拋異常:java.util.ConcurrentModificationException數組
可是下面這段代碼實際狀況卻沒報異常,是什麼狀況?post
1 List<String> list = new ArrayList<String>(); 2 list.add("1"); 3 list.add("2"); 4 for (String item : list) { 5 System.out.println("item:" + item); 6 if ("1".equals(item)) { 7 list.remove(item); 8 } 9 }
2、分析 this
咱們知道ArrayList的foreach迭代調用的是ArrayList內部類Itr,Itr源碼以下:url
1 private class Itr implements Iterator<E> { 2 int cursor; // index of next element to return 3 int lastRet = -1; // index of last element returned; -1 if no such 4 int expectedModCount = modCount; 5 6 public boolean hasNext() { 7 return cursor != size; 8 } 9 10 @SuppressWarnings("unchecked") 11 public E next() { 12 checkForComodification(); 13 int i = cursor; 14 if (i >= size) 15 throw new NoSuchElementException(); 16 Object[] elementData = ArrayList.this.elementData; 17 if (i >= elementData.length) 18 throw new ConcurrentModificationException(); 19 cursor = i + 1; 20 return (E) elementData[lastRet = i]; 21 } 22 23 public void remove() { 24 if (lastRet < 0) 25 throw new IllegalStateException(); 26 checkForComodification(); 27 28 try { 29 ArrayList.this.remove(lastRet); 30 cursor = lastRet; 31 lastRet = -1; 32 expectedModCount = modCount; 33 } catch (IndexOutOfBoundsException ex) { 34 throw new ConcurrentModificationException(); 35 } 36 } 37 38 final void checkForComodification() { 39 if (modCount != expectedModCount) 40 throw new ConcurrentModificationException(); 41 } 42 }
按照調用順序查看源碼spa
1.hasNext指針
ArrayList的foreach迭代首先調用的是ArrayList內部類Itr的hasNext(),該方法會對當前循環指針和長度作判斷。只有當hasNext()返回true纔會執行foreach裏面的代碼,cursor = size時候就退出循環(由於這二者相等意味着都遍歷完了,假如ArrayList的size=2,那麼hasNext會被調用3次,可是第3次調用不會執行foreach裏面代碼)。code
2.若是上一步返回true的話會執行Itr的next(),若是數據無異常的話 cursor = i + 1;因此沒執行一次next()時cursor都會+1htm
3.接着會執行到 list.remove(item),此處調用的是ArrayList的remove(Object o)而不是Itr的,看下ArrayList的remove()的源碼:
1 public boolean remove(Object o) { 2 if (o == null) { 3 for (int index = 0; index < size; index++) 4 if (elementData[index] == null) { 5 fastRemove(index); 6 return true; 7 } 8 } else { 9 for (int index = 0; index < size; index++) 10 if (o.equals(elementData[index])) { 11 fastRemove(index); 12 return true; 13 } 14 } 15 return false; 16 }
o != null,會進入else的fastRemove(index);能夠看到ArrayList根據傳入的值刪除會進行遍歷equals判斷,找到索引再經過fastRemove(index)刪除,所以List頻繁作刪除修改效率比較低。
4.再看下fastRemove()源碼:
1 */ 2 private void fastRemove(int index) { 3 modCount++; 4 int numMoved = size - index - 1; 5 if (numMoved > 0) 6 System.arraycopy(elementData, index+1, elementData, index, 7 numMoved); 8 elementData[--size] = null; // clear to let GC do its work 9 }
第8行會把該索引對應的數組的值置爲null,而且size-1。可是卻沒有進行 cursor - 1操做
至此明白了。此處的ArrayList經過foreach迭代刪除爲何不會報錯:
剛開始ArrayList的size=2時,cursor =0
①第一次hasNext()進來,cursor != size進入next()後cursor=1,接着由於知足條件刪除的時候size-1=1;
②第二次hasNext()進來,cursor = size = 1,因此不會執行foreach的代碼,也不會出現後面檢測modCount值拋ConcurrentModificationException
上述未拋異常的狀況主要是hasNext()中判斷遍歷完成的條件與ArrayList刪除後的數據恰好吻合而已。
因此只要知足條件:刪除的元素在循環時的指針cursor+1=size就會出現這種狀況!刪除ArrayList倒數第二個(即第 size - 1個元素)就會出現不拋異常的假象。
(例如size=3,刪除第2個元素;size=4,刪除第3個元素)
由於刪除後size-1=cursor
public boolean hasNext() {
return cursor != size;
}
hasNext()會返回false,不會進入ArrayList的迭代器,就不會進入 next() 執行checkForComodification()
這是一種條件判斷下的特殊狀況,其餘狀況都會拋出異常,因此不要在foreach進行刪除數據。請在迭代器中進行刪除。