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异常

你可能感兴趣的:(java基础知识)