for 、foreach 、iterator 三种遍历方式的比较

for 、foreach 、iterator 三种遍历方式的比较

习惯用法

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);
    }
}

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

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

image

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

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

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

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

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

image
image
image
image
image

你能够从中获取什么共性的特征呢?没错,也就是涉及到其中关于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)
image

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

且看定义:

image

lastRet的赋值场景

image

由上面代码可以看出,当你执行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方法,跟进入发现有一段代码

image

remove()方法:

image

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

后记:

上述文章主要介绍了 for循环、foreach 循环、iterator 迭代器遍历元素的速度大小的比较

还介绍了各自遍历过程中 对remove操作的影响。

如果你有什么问题或者好的建议,欢迎你与我一起讨论

| 作者:cxuan
出处:https://www.cnblogs.com/cxuanBlog/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则追究法律责任的权利。
如果文中有什么错误,欢迎指出。以免更多的人被误导。 |

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容