今天同事寫了幾行相似這樣的代碼:html
1
2
3
4
5
6
7
8
9
10
11
12
|
public
static
void
main(String args[]) {
List<String> famous =
new
ArrayList<String>();
famous.add(
"liudehua"
);
famous.add(
"madehua"
);
famous.add(
"liushishi"
);
famous.add(
"tangwei"
);
for
(String s : famous) {
if
(s.equals(
"madehua"
)) {
famous.remove(s);
}
}
}
|
運行出異常:java
Exception in thread "main" java.util.ConcurrentModificationExceptionshell
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)安全
at java.util.AbstractList$Itr.next(AbstractList.java:343)ide
at com.bes.Test.main(Test.java:15)this
Java新手最容易犯的錯誤,對JAVA集合進行遍歷刪除時務必要用迭代器。切記。idea
其實對於如上for循環,運行過程當中仍是轉換成了以下代碼:spa
1
2
3
4
5
6
|
for
(Iterator<String> it = famous.iterator();it.hasNext();){
String s = it.next();
if
(s.equals(
"madehua"
)){
famous.remove(s);
}
}
|
仍然採用的是迭代器,但刪除操做卻用了錯誤的方法。如將famous.remove(s)改爲it.remove().net
則運行正常,結果也無誤。線程
固然若是改爲:
1
2
3
4
5
6
|
for
(
int
i =
0
; i < famous.size(); i++) {
String s = famous.get(i);
if
(s.equals(
"madehua"
)) {
famous.remove(s);
}
}
|
這種方法,也是能夠完成功能,但通常也不這麼寫。
爲何用了迭代碼器就不能採用famous.remove(s)操做? 這種由於ArrayList與Iterator混合使用時會致使各自的狀態出現不同,最終出現異常。
咱們看一下ArrayList中的Iterator實現:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
private
class
Itr
implements
Iterator<E> {
/**
* Index of element to be returned by subsequent call to next.
*/
int
cursor =
0
;
/**
* Index of element returned by most recent call to next or
* previous. Reset to -1 if this element is deleted by a call
* to remove.
*/
int
lastRet = -
1
;
/**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
int
expectedModCount = modCount;
public
boolean
hasNext() {
return
cursor != size();
}
public
E next() {
checkForComodification();
try
{
E next = get(cursor);
lastRet = cursor++;
return
next;
}
catch
(IndexOutOfBoundsException e) {
checkForComodification();
throw
new
NoSuchElementException();
}
}
public
void
remove() {
if
(lastRet == -
1
)
throw
new
IllegalStateException();
checkForComodification();
try
{
AbstractList.
this
.remove(lastRet);
if
(lastRet < cursor)
cursor--;
lastRet = -
1
;
expectedModCount = modCount;
}
catch
(IndexOutOfBoundsException e) {
throw
new
ConcurrentModificationException();
}
}
final
void
checkForComodification() {
if
(modCount != expectedModCount)
throw
new
ConcurrentModificationException();
}
}
|
基本上ArrayList採用size屬性來維護自已的狀態,而Iterator採用cursor來來維護自已的狀態。
當size出現變化時,cursor並不必定可以獲得同步,除非這種變化是Iterator主動致使的。
從上面的代碼能夠看到當Iterator.remove方法致使ArrayList列表發生變化時,他會更新cursor來同步這一變化。但其餘方式致使的ArrayList變化,Iterator是沒法感知的。ArrayList天然也不會主動通知Iterator們,那將是一個繁重的工做。Iterator到底仍是作了努力:爲了防止狀態不一致可能引起的沒法設想的後果,Iterator會常常作checkForComodification檢查,以防有變。若是有變,則以異常拋出,因此就出現了上面的異常。
若是對正在被迭代的集合進行結構上的改變(即對該集合使用add、remove或clear方法),那麼迭代器就再也不合法(而且在其後使用該迭代器將會有ConcurrentModificationException異常被拋出).
若是使用迭代器本身的remove方法,那麼這個迭代器就仍然是合法的。
package chapter1; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * Created by MyWorld on 2016/3/3. */ public class FastFailResolver { public static void main(String[] args) { Map<String, String> source = new HashMap<String, String>(); for (int i = 0; i < 10; i++) { source.put("key" + i, "value" + i); } System.out.println("Source:" + source); // fastFailSceneWhenRemove(source); commonSceneWhenRemove(source); } private static void commonSceneWhenRemove(Map<String, String> source) { Iterator<Map.Entry<String, String>> iterator = source.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, String> entry = iterator.next(); if (entry.getKey().contains("1")) { iterator.remove(); } } System.out.println(source); } private static void fastFailSceneWhenRemove(Map<String, String> source) { for (Map.Entry<String, String> entry : source.entrySet()) { if (entry.getKey().contains("1")) { source.remove(entry.getKey()); } } System.out.println(source); } }
思考下面這一段在循環中刪除多個元素的的代碼
1
2
3
4
5
|
ArrayList<String> list =
new
ArrayList<String>(Arrays.asList(
"a"
,
"b"
,
"c"
,
"d"
));
for
(
int
i=
0
;i<list.size();i++){
list.remove(i);
}
System.out.println(list);
|
輸出結果是:
1
|
[b,d]
|
在這個方法中有一個嚴重的錯誤。當一個元素被刪除時,列表的大小縮小而且下標變化,因此當你想要在一個循環中用下標刪除多個元素的時候,它並不會正常的生效。
與下面結合的一個示例:
public static void main(String[] args) { ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","a", "b", "c", "d")); for (int i = 0; i < list.size(); i++) { if (list.get(i).equals("a")) { list.remove(i); } } System.out.println(list); }
輸出:
[a, b, c, d]
即輸出與預期不一致
你也許知道在循環中正確的刪除多個元素的方法是使用迭代,而且你知道java中的foreach循環看起來像一個迭代器,但實際上並非。考慮一下下面的代碼:
1
2
3
4
5
6
|
ArrayList<String> list =
new
ArrayList<String>(Arrays.asList(
"a"
,
"b"
,
"c"
,
"d"
));
for
(String s:list){
if
(s.equals(
"a"
)){
list.remove(s);
}
}
|
它會拋出一個ConcurrentModificationException異常。 相反下面的顯示正常:
1
2
3
4
5
6
7
8
|
ArrayList<String> list =
new
ArrayList<String>(Arrays.asList(
"a"
,
"b"
,
"c"
,
"d"
));
Iterator<String> iter = list.iterator();
while
(iter.hasNext()){
String s = iter.next();
if
(s.equals(
"a"
)){
iter.remove();
}
}
|
.next()
必須在.remove()
以前調用。在一個foreach循環中,編譯器會使.next()在刪除元素以後被調用,所以就會拋出ConcurrentModificationException異常,你也許但願看一下ArrayList.iterator()的源代碼。
http://www.cnblogs.com/softidea/p/4279574.html
import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class IteratorTest{ public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("Test1"); list.add("Test2"); list.add("Test3"); list.add("Test4"); list.add("Test5"); for(Iterator<String> it = list.iterator();it.hasNext();){ if(it.next().equals("Test3")){ it.remove(); } } for(String s : list){ System.out.println(s); } } }
Iterator 支持從源集合中安全地刪除對象,只需在 Iterator 上調用 remove() 便可。這樣作的好處是能夠避免 ConcurrentModifiedException ,這個異常顧名思意:當打開 Iterator 迭代集合時,同時又在對集合進行修改。
有些集合不容許在迭代時刪除或添加元素,可是調用 Iterator 的remove() 方法是個安全的作法。
java.util.ConcurrentModificationException詳解
http://blog.csdn.net/smcwwh/article/details/7036663
常常在迭代集合元素時,會想對集合作修改(add/remove)操做,相似下面這段代碼:
for (Iterator<Integer> it = list.iterator(); it.hasNext(); ) { Integer val = it.next(); if (val == 5) { list.remove(val); } }
運行這段代碼,會拋出異常java.util.ConcurrentModificationException。
【解惑】
(以ArrayList來說解)在ArrayList中,它的修改操做(add/remove)都會對modCount這個字段+1,modCount能夠看做一個版本號,每次集合中的元素被修改後,都會+1(即便溢出)。接下來再看看AbsrtactList中iteraor方法
public Iterator<E> iterator() { return new Itr(); }
它返回一個內部類,這個類實現了iterator接口,代碼以下:
private class Itr implements Iterator<E> { int cursor = 0; int lastRet = -1; int expectedModCount = modCount; public boolean hasNext() { return cursor != size(); } public E next() { checkForComodification(); try { E next = get(cursor); lastRet = cursor++; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } public void remove() { if (lastRet == -1) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; // 修改expectedModCount 的值 expectedModCount = modCount; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
在內部類Itr中,有一個字段expectedModCount ,初始化時等於modCount,即當咱們調用list.iterator()返回迭代器時,該字段被初始化爲等於modCount。在類Itr中next/remove方法都有調用checkForComodification()方法,在該方法中檢測modCount == expectedModCount,若是不至關則拋出異常ConcurrentModificationException。
前面說過,在集合的修改操做(add/remove)中,都對modCount進行了+1。
在看看剛開始提出的那段代碼,在迭代過程當中,執行list.remove(val),使得modCount+1,當下一次循環時,執行 it.next(),checkForComodification方法發現modCount != expectedModCount,則拋出異常。
【解決辦法】
若是想要在迭代的過程當中,執行刪除元素操做怎麼辦?
再來看看內部類Itr的remove()方法,在刪除元素後,有這麼一句expectedModCount = modCount,同步修改expectedModCount 的值。因此,若是須要在使用迭代器迭代時,刪除元素,可使用迭代器提供的remove方法。對於add操做,則在整個迭代器迭代過程當中是不容許的。 其餘集合(Map/Set)使用迭代器迭代也是同樣。
當使用 fail-fast iterator 對 Collection 或 Map 進行迭代操做過程當中嘗試直接修改 Collection / Map 的內容時,即便是在單線程下運行, java.util.ConcurrentModificationException 異常也將被拋出。
Iterator 是工做在一個獨立的線程中,而且擁有一個 mutex 鎖。
Iterator 被建立以後會創建一個指向原來對象的單鏈索引表,當原來的對象數量發生變化時,這個索引表的內容不會同步改變,因此當索引指針日後移動的時候就找不到要迭代的對象,因此按照 fail-fast 原則 Iterator 會立刻拋出 java.util.ConcurrentModificationException 異常。
因此 Iterator 在工做的時候是不容許被迭代的對象被改變的。
但你可使用 Iterator 自己的方法 remove() 來刪除對象, Iterator.remove() 方法會在刪除當前迭代對象的同時維護索引的一致性。
有意思的是若是你的 Collection / Map 對象實際只有一個元素的時候, ConcurrentModificationException 異常並不會被拋出。這也就是爲何在 javadoc 裏面指出: it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs.