Java三种遍历方式

for、foreach循环、iterator迭代器都是我们常用的一种遍历方式,你可以用它来遍历任何东西:包括数组、集合等

for 惯用法:

List<String> list = new ArrayList<String>();
String[] arr = new String[]{"1,2,3,4"};
for(int i = 0;i < arr.length;i++){
System.out.println(arr[i]);
}
for(int i = 0;i < list.size();i++){
System.out.println(list.get(i));
}

优缺点分析:

  • 优点:效率最高,遍历快,可以根据自定计数器操作元素

  • 缺点:不适用所有集合,适用范围小

foreach 惯用法:

String[] arr = new String[]{"1,2,3,4"};
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
for(String str : arr){
System.out.println(str);
}
for (String item : list) {
System.out.println(item);
}

优缺点分析:

  • 优点:代码简洁,不易出错。

  • 缺点:只能做简单的遍历,不能在遍历过程中操作(删除、替换)数据集合。

Iterator 惯用法:

Iterator<String> it = list.iterator();
while (it.hasNext()){
System.out.println(it.next());
}

优缺点分析:

  • 优点:迭代器提供了操作元素的方法 可以在遍历中相应地操作元素

  • 缺点:运行复杂,性能稍差,效率相对其他两种遍历方式较低

                                                                                            速度对比

    性能是我们选取某一种技术手段的一种考虑方式,且看这三种遍历方式的速度对比

    List<Long> list = new ArrayList<Long>();
    long maxLoop = 2000000;
    for(long i = 0;i < maxLoop;i++){
    list.add(i);
    }

    // for循环
    long startTime = System.currentTimeMillis();
    for(int i = 0;i < list.size();i++){
    ;
    }
    long endTime = System.currentTimeMillis();
    System.out.println(endTime - startTime + "ms");

    // foreach 循环
    startTime = System.currentTimeMillis();
    for(Long lon : list){
    ;
    }
    endTime = System.currentTimeMillis();
    System.out.println(endTime - startTime + "ms");

    // iterator 循环
    startTime = System.currentTimeMillis();
    Iterator<Long> iterator = list.iterator();
    while (iterator.hasNext()) {
    iterator.next();
    }
    endTime = System.currentTimeMillis();
    System.out.println(endTime - startTime + "ms");

    4ms 16ms 9ms

    由以上得知,for()循环是最快的遍历方式,随后是iterator()迭代器,最后是foreach循环

                                                                                 remove操作三种遍历

    for循环的remove

    List<String> list = new ArrayList<String>();
    list.add("1");
    list.add("2");
    for(int i = 0;i < list.size();i++){
    if("2".equals(list.get(i))){
    System.out.println(list.get(i));
    list.remove(list.get(i));
    }
    }

    for循环可以直接进行remove,不会受到任何影响。

    foreach 中的remove

    List<String> list = new ArrayList<String>();
    list.add("1");
    list.add("2");
    for (String item : list) {
    if ("2".equals(item)) {
    System.out.println(item);
    list.remove(item);
    }
    }

    你觉得这段代码的正确输出是什么?我们一起来探究一下

    当我执行一下这段代码的时候,出现了以下的情况

    由以上异常情况的堆栈信息得知,程序出现了并发修改的异常,为什么会这样?我们从错误开始入手,

    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)

    也就是这行代码,找到这行代码的所在地

    final void checkForComodification() {
    if (modCount != expectedModCount)
    throw new ConcurrentModificationException();
    }

    你会好奇, modCount 和 expectedModCount 是什么变量?在我对 ArrayList 相关用法那篇文章中有比较详细的解释。我大致说明一下:modCount 相当于是程序所能够进行修改 ArrayList 结构化的一个变量,怎么理解?看几个代码片段

    你能够从中获取什么共性的特征呢?没错,也就是涉及到其中关于ArrayList的 容量大小 和 __元素个数__的时候,就会触发modCount 的值的变化

    expectedModCount这个变量又是怎么回事?从ArrayList 源码可知,这个变量是一个局部变量,也就是说每个方法内部都有expectedModCount 和 modCount 的判断机制,进一步来讲,这个变量就是 预期的修改次数

    先抛开这个不谈,我们先来谈论一下foreach(增强for循环)本身。

    增强for循环是Java给我们提供的一个语法糖,如果将以上代码编译后的class文件进行反编译(使用jad工具)的话,可以得到以下代码:

    terator iterator = item.iterator();

    也就是说,其实foreach 每次循环都调用了一次iterator的next()方法

    因此才会有这个堆栈信息:

    at java.util.ArrayList$Itr.next(ArrayList.java:859)

    下面我们来尝试分析一下这段代码报错的原因:

    1、第一次 以 “1”的值进入循环,"1" != "2", 执行下一次循环

    2、第二次循环以"2"的值进入,判断相等,执行remove()方法(注意这个remove方法并不是 iterator的remove(),而是ArrayList的remove()方法),导致modCount++

    3、再次调用next()的时候,modCount != expectedModCount ,所以抛出异常

    Iterator迭代器的remove

    使用迭代器进行遍历还有很多需要注意的地方:

    正确的遍历

    List<String> list = new ArrayList<String>();
    list.add("1");
    list.add("2");
    list.add("3");
    Iterator<String> it = list.iterator();
    while (it.hasNext()){
    System.out.println(it.next());
    it.remove();
    }

    这是一种正确的写法,如果输出语句和 remove()方法互换顺序怎么样呢?

    错误的遍历 —— next() 和 remove() 执行顺序的问题

    List<String> list = new ArrayList<String>();
    list.add("1");
    list.add("2");
    list.add("3");
    Iterator<String> it = list.iterator();
    while (it.hasNext()){
    it.remove();
    System.out.println(it.next());
    }

    执行程序输出就会报错:

    Exception in thread "main" java.lang.IllegalStateException
    at java.util.ArrayList$Itr.remove(ArrayList.java:872)
    at test.SimpleTest.main(SimpleTest.java:46)

    这又是为什么?还是直接从错误入手:

    定位到错误的位置

    at java.util.ArrayList$Itr.remove(ArrayList.java:872)

    发现如果 lastRet 的值小于 0就会抛出非法状态的异常,这个lastRet是什么?

    且看定义:

    由上面代码可以看出,当你执行next()方法的时候, lastRet 赋值为i,所以这个elementData[]中的下标最小是0,所以这个时候lastRet 最小的值是0, 那么只有当执行remove()方法的时候,lastRet的值赋值为-1,也就是说,你必须先执行一次next方法,再执行一次remove方法,才能够保证程序的正确运行。

    错误的遍历 —— 使用Arrays.asList()

    List<String> list = Arrays.asList("1","2","3");
    Iterator<String> it = list.iterator();
    while (it.hasNext()){
    System.out.println(it.next());
    it.remove();
    }

    这段代码执行之后的输出是怎样的呢?

    1
    Exception in thread "main" java.lang.UnsupportedOperationException
    at java.util.AbstractList.remove(AbstractList.java:161)
    at java.util.AbstractList$Itr.remove(AbstractList.java:374)
    at test.SimpleTest.main(SimpleTest.java:50)

    很不幸,这段代码也抛出了异常,直接从错误处入手发现,这个remove()方法调用的是AbstractList中的remove方法,跟进入发现有一段代码

    remove()方法:

    也就是说,只要这段代码执行了,都会报错,抛出异常

    各种遍历方式适用场合:

  • 1、传统的for循环遍历,基于计数器的:

    • 顺序存储:读取性能比较高。适用于遍历顺序存储集合。

    • 链式存储:时间复杂度太大,不适用于遍历链式存储的集合。

  • 2、迭代器遍历,Iterator:

    • 顺序存储:如果不是太在意时间,推荐选择此方式,毕竟代码更加简洁,也防止了Off-By-One的问题。

    • 链式存储:意义就重大了,平均时间复杂度降为O(n),还是挺诱人的,所以推荐此种遍历方式。

  • 3、foreach循环遍历:
           foreach只是让代码更加简洁了,但是他有一些缺点,就是遍历过程中不能操作数据集合(删除等),所以有些场合不使用。而且它本身就是基于Iterator实现的,但是由于类型转换的问题,所以会比直接使用Iterator慢一点,但是还好,时间复杂度都是一样的。所以怎么选择,参考上面两种方式,做一个折中的选择。


全部评论