迭代器与


75

在一次采访中有人问我,使用迭代器而不是for循环的优点是for什么?

有人可以回答吗?


2
for-each循环在幕后使用迭代器。区别仅在于可读性(以及因此的可维护性)。
达伍德·伊本·卡里姆

1
好; 你为什么这么说Iterator
蜘蛛鲍里斯(Boris)

3
没什么迫使您立即回答。您可以对不同种类的集合进行推理,每个代码在幕后做什么,并显示出推理的能力,即使您无法制定明确的答案。那才是重要的:能够思考。我将从以下答案开始:“恩,我不知道。我想这取决于您要执行的操作以及收集的种类。必须有充分的理由同时使用这两种方法,但这必须取决于这种情况。让我们想象一下,使用ArrayList会发生什么。然后是LinkedList等。”
JB Nizet 2014年

3
我完全同意@JBNizet。说我并不比选择随机选项好得多-这给我的印象是您在编程时会这样做;不用思考就选择想到的第一件事。您可以尝试说出自己在该领域的知识,而不是说自己不足以给出明智的答案。
蜘蛛鲍里斯(Boris)

2
当前的两个答案都不完整。首先,有两种for循环,它们的行为非常不同。一个使用索引(并非总是可能),而另一个使用幕后的Iterator。因此,实际上您有3个循环要比较。然后,您可以用不同的术语进行比较:性能,可读性,易错性,功能。迭代器可以执行foreach循环无法执行的操作。但是,迭代器更危险,可读性更差。使用索引访问元素仅对列表有效,但如果是链表则效率不高。因此,没有“最佳”方法。
JB Nizet 2014年

Answers:


129

首先,有两种for循环,它们的行为非常不同。一种使用索引:

for (int i = 0; i < list.size(); i++) {
    Thing t = list.get(i);
    ...
}

这种循环并非总是可能的。例如,列表具有索引,而集合没有索引,因为它们是无序集合。

另一个foreach循环在幕后使用Iterator:

for (Thing thing : list) {
    ...
}

这适用于每种Iterable集合(或数组)

最后,您可以使用Iterator,它也可以与任何Iterable一起使用:

for (Iterator<Thing> it = list.iterator(); it.hasNext(); ) {
    Thing t = it.next();
    ...
} 

因此,实际上您有3个循环要比较。

您可以用不同的术语来比较它们:性能,可读性,易错性,功能。

迭代器可以执行foreach循环无法执行的操作。例如,如果迭代器支持,则可以在迭代时删除元素:

for (Iterator<Thing> it = list.iterator(); it.hasNext(); ) {
    Thing t = it.next();
    if (shouldBeDeleted(thing) {
        it.remove();
    }
} 

列表还提供可以双向迭代的迭代器。foreach循环仅从头到尾进行迭代。

但是,迭代器更危险,可读性更差。当您只需要一个foreach循环时,它就是最易读的解决方案。使用迭代器,您可以执行以下操作,这将是一个错误:

for (Iterator<Thing> it = list.iterator(); it.hasNext(); ) {
    System.out.println(it.next().getFoo());
    System.out.println(it.next().getBar());
} 

foreach循环不允许发生此类错误。

对于数组支持的集合,使用索引访问元素的效率稍高。但是,如果您改变主意并使用LinkedList而不是ArrayList,则性能会突然变差,因为每次访问时list.get(i),链表都必须循环遍历所有元素,直到第i个元素为止。迭代器(以及foreach循环)不存在此问题。它始终使用最佳方法来遍历给定集合的元素,因为集合本身具有自己的Iterator实现。

我的一般经验法则是:使用foreach循环,除非您确实需要Iterator的功能。当我需要访问循环内的索引时,我只会使用带有数组索引的for循环。


当您使用Iterator删除集合中的一个或多个元素时,这是安全的。当您使用for-each或for循环时,不允许删除或导致错误,因为在循环访问集合时进行删除时,集合中每个元素的索引都会更改。因此,您应该使用反向for循环,或使用迭代器来防止引入错误。另请参阅此答案
user504342's

2
您说的“ ith”是英语的挑战。
MDP

1
也许他可以用“ i-th”代替它?
米罗斯拉夫·托多罗夫

17

迭代器优势:

  • 能够从集合中删除元素。
  • 能够向前和向后移动 next()previous()
  • 通过使用检查是否有更多元素的能力 hasNext()

循环仅设计为对a进行迭代Collection,因此,如果只想对a进行迭代Collection,则最好使用,例如for-Each,但如果希望更多,则可以使用Iterator。


1
并且,ListIterator您还可以add在任意点开始迭代。
蜘蛛鲍里斯(Boris)

@Salah:为什么迭代器可以在迭代集合时删除元素?
logbasex

12

如果按数字访问数据(例如“ i”),则使用数组时速度很快。因为它直接进入元素

但是,其他数据结构(例如树,列表)需要更多时间,因为它从第一个元素开始到目标元素。使用清单时。它需要时间O(n)。因此,它要慢一些。

如果使用迭代器,则编译器会知道您所在的位置。所以它需要O(1)(因为它从当前位置开始)

最后,如果仅使用支持直接访问的数组或数据结构(例如,java中的arraylist)。“ a [i]”很好。但是,当您使用其他数据结构时,迭代器效率更高


所以我应该说这取决于要求。
2014年

+1指出在Java上使用按索引循环的缺点Collections。值得指出的是,增强的for循环Iterator在幕后使用。
蜘蛛鲍里斯(Boris)

您的其他数据结构示例无济于事-ArrayList提供了索引访问。也许进行更新以包括特定的Java集合?(例如ArrayList与LinkedList)
Richard Miskin 2014年

11

除了可以访问或不访问要迭代的项目的索引这一明显的区别之外,Iterator和经典的for循环之间的主要区别在于,使用Iterator可以从底层集合实现中抽象出客户端代码,阐述。

当您的代码使用迭代器时,采用以下形式

for(Item element : myCollection) { ... }

这种形式

Iterator<Item> iterator = myCollection.iterator();
while(iterator.hasNext()) {    
    Item element = iterator.next();    
    ... 
}

或这种形式

for(Iterator iterator = myCollection.iterator(); iterator.hasNext(); ) {
   Item element = iterator.next();
   ...
}

您的代码说的是“我不在乎集合的类型及其实现,我只是在乎可以迭代其元素”。通常这是更好的方法,因为它会使您的代码更加分离。

另一方面,如果您使用经典的for循环,例如

for(int i = 0; i < myCollection.size(); i++) {
   Item element = myCollection.get(i);
   ...
}

您的代码说,我需要知道集合的类型,因为我需要以特定的方式遍历集合的元素,因此我还可能要检查空值或根据迭代顺序计算一些结果。这使您的代码更加脆弱,因为如果在任何时候您收到的集合类型发生变化,都会影响代码的工作方式。

总结起来,区别不在于速度或内存使用,更多的在于解耦代码,以便更灵活地应对更改。


2

与其他答案不同,我想指出另一件事。

如果您需要在代码中的多个位置执行迭代,则最终可能会重复逻辑。显然,这不是一个非常可扩展的方法。相反,需要的是一种从实际处理代码的代码中分离出用于选择数据的逻辑的方法。

一个迭代诸如基于阵列被隐藏-通过用于循环在一组数据,使得底层数据结构或存储机制提供的通用接口来解决这些问题。

  • 迭代器是一个概念,而不是实现。
  • 迭代器提供许多用于遍历和访问数据的操作。
  • 迭代器可以包装任何数据结构,例如数组。
  • 使用迭代器的更有趣和有用的优点之一是可以包装或装饰另一个迭代器以过滤返回值的功能
  • 迭代器可能是线程安全的,而单独使用for循环则不能,因为它直接访问元素。唯一流行的线程安全迭代器是CopyOnWriteArrayList但是它是众所周知的,并且经常被使用,因此值得一提。

这是来自这本书的https://www.amazon.com/Beginning-Algorithms-Simon-Harris/dp/0764596748


0

我偶然发现了这个问题。答案在于Iterator试图解决的问题:

  • 在不暴露其表示的情况下访问和遍历聚合对象的元素
  • 在不更改其接口的情况下定义聚合对象的遍历操作
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.