1. 程式人生 > >java集合中的遍歷方法

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的話,expectedModCountmodCount是始終相等的。

Itr.remove方法中,呼叫了外部類ArrayList.this.remove(lastRet)方法,之前分析過,把modCount次數加1,但是又設定expectedModCount = modCount,所有下次呼叫itr.remove方法時候expectedModCountmodCount還是相等的,所以測試案例呼叫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中的hasNextnext方法,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異常