java集合中的遍歷方法
java集合中的遍歷方法
一般在遍歷java集合的時候有三種方式:for-loop、增強for和iterator
public static void forEnhancedMethod(){
for (String str: list){
System.out.println(str);
}
}
public static void forLoopMethod(){
for (int i=0; i < list.size(); i++)
System.out.println(list.get(i));
}
}
public static void iteratorMethod(){
Iterator<String> itr = list.listIterator();
while (itr.hasNext()){
String el = itr.next();
System.out.println(el);
}
}
使用iterator遍歷集合的時候是有fail-fast
機制的,也就是在遍歷的時候,如果集合的結構發生變化,就會丟擲ConcurrentModificationException
異常,那麼問題來了,就比較一下有一種方式遍歷方式在改變了結構的情況下到底有哪些是會丟擲ConcurrentModificationException
準備
先定義一個集合
public class ForeachIterator {
public static final ArrayList<String> list =
new ArrayList(Arrays.asList("a", "b", "c", "d", "e"));
//....
}
首先要明白ArrayList
內部儲存資料是由Object[] elementData
陣列維護的
for-Loop形式
我們準備刪除c
資料:
public static void forLoopMethod(){
for (int i=0; i < list.size(); i++){
if (i == 2){
list.remove(i);
System.out.println("remove list(2)");
}else {
System.out.println(list.get(i));
}
}
}
輸出結果:
a
b
remove list(2)
e
什麼情況,明明只刪除了一個元素,最後輸出只有a,b,e
,…
那麼再看一組測試,嘗試刪除list中所有的元素:
public static void forLoopMethod(){
for (int i=0; i < list.size(); i++){
list.remove(i);
}
System.out.println("list size: "+list.size());
System.out.println(list);
}
輸出結果:
list size: 2
[b, d]
也是一臉懵逼,明明是刪除所有元素…
幸運的是在刪除資料的時候沒有丟擲ConcurrentModificationException
異常,傻了吧,只有使用iterator遍歷集合才有可能丟擲ConcurrentModificationException
異常,for-Loop
是不會出現這樣的情況啦
現在來看下list.remove(index)
這個方法原始碼:
public E remove(int index) {
//檢測是否陣列越界
rangeCheck(index);
//修改次數加1
modCount++;
E oldValue = elementData(index);
//計算在刪除元素後後面元素要向前移動的步長
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//設定最後一個元素為null,並size減1
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
那麼這就不足為怪了,每刪除一個元素,陣列中後面元素就向前移動一步,就假設我們要刪除list中資料c
,它的下標是2,也就是i=2,刪除了c
之後,d,e
都從陣列後面向前移動了一步,即d
到了c
之後,e
移到了原來d
的位置,但是這時的取到下一個元素的時候,此時i卻成了3(因為進行了i++操作),所以取到的值就是elementData[3]
的值,而此時elementData[3]
的值是e
,所以就看到了第一個測試結果了。這也就不難解釋當用for-Loop
遍歷刪除所有資料的時候list.size
的大小為2的結果了,而且輸出的結果是呈跳躍式的了
因此,不建議使用for-Loop方式去遍歷刪除list中的元素,這樣的話,資料可能刪除不完
Iterator方式
public static void iteratorMethod(){
Iterator<String> itr = list.listIterator();
while (itr.hasNext()){
String el = itr.next();
if (el.equals("c")){
itr.remove();
System.out.println("remove c");
}else {
System.out.println(el);
}
}
}
輸出結果:
a
b
remove c
d
e
再看一組測試,這個測試案例和上一個不同在於將itr.remove()
換成了list.remove("c")
:
public static void iteratorMethod(){
Iterator<String> itr = list.listIterator();
while (itr.hasNext()){
String el = itr.next();
if (el.equals("c")){
list.remove("c");
System.out.println("remove c");
}else {
System.out.println(el);
}
}
}
輸出結果:
a
b
remove c
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
結果很明顯,出現了我們期待已久的ConcurrentModificationException
異常
唯一不同的就是將將itr.remove()
換成了list.remove("c")
,而第二個卻拋異常了,list.remove(object)
方法和list.remove(index)
方法一樣,所以就不用看了,現在看下itr.remove
方法,ArrayList
中Iterator的實現:
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;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
//檢測list是否發生了結構化修改
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
//cursor變成了下一次返回資料的下標
cursor = i + 1;
//返回當前資料
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
//檢測list是否發生了結構化修改
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
這裡要知道modCount
就是引起list發生結構化修改(structurally modified)
的次數,比如list發生了add
或者是remove
操作都會引起list發生structurally modified
變化。如果不發生structurally modified
的話,expectedModCount
和modCount
是始終相等的。
在Itr.remove
方法中,呼叫了外部類ArrayList.this.remove(lastRet)
方法,之前分析過,把modCount
次數加1,但是又設定expectedModCount = modCount
,所有下次呼叫itr.remove
方法時候expectedModCount
和modCount
還是相等的,所以測試案例呼叫itr.remove
方法去刪除list中資料的時候,並不會丟擲ConcurrentModificationException
異常
而在第二個測試中是直接呼叫list.remove(index)
方法,我們知道list.remove(index)
會將modCount
次數加1,使得下次呼叫itr.next
方法時會呼叫checkForComodification
方法來檢測list是否發生了structurally modified
變化,此時顯然是不相等的,所以丟擲了ConcurrentModificationException
異常
增強for方式
public static void forEnhancedMethod(){
for (String str: list){
if (str.equals("c")){
list.remove("c");
System.out.println("remove list(c)");
}else {
System.out.println(str);
}
list.remove(str);
}
}
輸出結果:
a
b
remove list(c)
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
我們再看一組測試案例,將刪除資料c
換成刪除資料d
:
public static void forEnhancedMethod(){
for (String str: list){
if (str.equals("d")){
list.remove("d");
System.out.println("remove list(d)");
}else {
System.out.println(str);
}
}
}
輸出結果:
a
b
c
remove list(d)
也是一臉懵逼啊…
先看下forEnhancedMethod
方法反編譯之後:
public static void forEnhancedMethod() {
Iterator var0 = list.iterator();
while(var0.hasNext()) {
String str = (String)var0.next();
if (str.equals("d")) {
//這裡不同!!
list.remove("d");
System.out.println("remove list(d)");
} else {
System.out.println(str);
}
}
}
可以發現我們增強for
方式最終轉成了iterator方式執行,只是刪除的時候不是採用itr.remove
方式,所以第一組測試案例會發生ConcurrentModificationException
異常了。
下面就是看看是什麼原因導致第二組測試案例結果了這麼神奇,再仔細看下,這個資料d
是在list中倒數第二,在遍歷的時候我們有:
while(itr.hasNext()) {
//....
}
再看下Itr中的hasNext
和next
方法,hasNext
方法發現cursor != size
時就會終止迴圈,而在itr.next
的方法執行之後就會將cursor
後移一位。再結合之前要刪除資料d
,這是倒數第二個資料,當遍歷到資料d
,呼叫itr.next
方法之後,經過list.remove(object)
後,list中的size減1,即cursor==list.size=4
。當要遍歷資料e
時,執行itr.hasNext
時發現cursor==size
返回false
,所以就終止了遍歷,沒有輸出最後資料e
。
結論
for-Loop方式
刪除資料時會跳躍的刪除,所以不建議使用,但不會丟擲ConcurrentModificationException
異常增強-for方式
在執行刪除的時候會轉成Iterator執行,但實際呼叫list.remove(object)
方法,會導致ConcurrentModificationException
異常iterator方式
刪除資料時,如果呼叫itr.remove
方法就不會以導致ConcurrentModificationException
異常,但是如果呼叫list.remove(object)
方法也還是會丟擲ConcurrentModificationException
異常