List<T> list = new ArrayList<T>();
1方法:
for(int i = list.length - 1; i >= 0; i--) {
System.out.println(list.get(i));
}
2方法:
for(T t : list) {
System.out.println(t);
}
3方法:
Iterator<T> it = list.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
答案 0 :(得分:12)
效率不太可能显着 - 当然System.out.println
更可能是您特定示例中的瓶颈。
第二种方法(针对循环增强)是最可读。请注意,这三种方法不会做同样的事情 - 第一种方法将从 end 而不是 start 进行迭代。获得正确的行为几乎总是胜过少量,快一点。您的代码越可读,您就越有可能做到正确。
寻求可读性,衡量应用的性能,如果成为问题,微观优化瓶颈(继续衡量每一步)。
编辑:我的回答是在问题的第一行,显示正在使用ArrayList<T>
。
如果您想要任何 List<T>
的答案,那么根本就没有准确的答案。 List<T>
不提供任何有关复杂性的保证。它没有说明你期望get
的速度有多快,也没有说你期望迭代器的速度有多快。您可以使用具有良好随机访问权限的实现,但迭代速度非常慢。这不太可能,但它仍然是有效的List<T>
。
简而言之:如果你担心效率,你需要知道你正在使用什么样的列表。 通常迭代它可能是合理有效的,并且随机访问可能会或可能不会有效。 (它可能比迭代更有效,但它不太可能显着更高效。)在实践中,我的经验是,在大多数应用程序中,你实际上有足够的知识来做出合理的判断......但是我通常仍然会首先编写可读性代码。
正如其他人所指出的,如果你做想要使用随机访问来获取元素,那么确保你的列表也实现RandomAccess
接口是值得的。 / p>
答案 1 :(得分:5)
如果你有一个没有随机访问支持的长列表,第一种方法会慢得多。例如。在LinkedList上调用get(1000000)将必须从列表的开头循环1000000个元素以到达1000000th元素。第二种和第三种方法应该是等价的。
如果它与您的情况相关,那么提供对其元素的持续时间随机访问的List实现应该重写java.util.RandomAccess标记接口。至少标准API中的List实现可以。
答案 2 :(得分:5)
方法1不太可能是最好的方法,因为它没有其他方面的可读性或性能优势。
如果您想跟踪索引,请考虑使用ListIterator
代替Iterator
,例如。
ListIterator<T> it = list.listIterator();
while (it.hasNext()) {
T t = it.next;
int index = it.previousIndex();
System.out.printf("%d => %s%n", index, t);
}
正如其他人所指出的,方法3相当于方法2,除非你想在迭代过程中从列表中删除元素。
如果分析显示列表的get(int)
方法或迭代器的next()
或hasNext()
是性能瓶颈(非常罕见但可能发生),请考虑用数组替换列表(您仍然可以使用for-each语法,类似于方法2.)
答案 3 :(得分:2)
RandomAccess
java.util.ArrayList
实施RandomAccess
。文档清楚地表明了这意味着什么:
List
实现使用的标记接口,表示它们支持快速(通常是恒定时间)随机访问。 [...]根据经验,List
实现应该实现此接口,如果对于类的典型实例,此循环:for (int i=0, n=list.size(); i < n; i++) list.get(i);
比这个循环运行得更快:
for (Iterator i=list.iterator(); i.hasNext(); ) i.next();
因此,对于正确 List
的{{1}},索引 - implements RandomAccess
更快。
但请注意,对于get
来说, NOT 为true,其中上述代码表现出二次性能。这是因为LinkedList
不允许进行常时随机访问;也就是说,LinkedList
对get
是线性的。
请注意,即使索引 - LinkedList
更快,它也只会是一个常数因子。首先查看此优化尝试是否值得。
get
vs for-each
循环您应该关注这两种结构的性能没有显着差异。 Iterator
循环更具可读性,但适用于更有限的场景,因此恰恰在这些场景中应该使用它们。如果for-each
循环不适用,请使用for-each
循环。
引用 Effective Java 2nd Edition,第46项:首选Iterator
循环到传统的for-each
循环:
1.5版中引入的
for
循环消除了混乱 以及隐藏迭代器或索引变量的错误机会 完全。由此产生的习语同样适用于集合和数组:for-each
当您看到冒号(:)时,请将其读作“in”。因此,上面的循环读作为 “对于元素中的每个元素
// The preferred idiom for iterating over collections and arrays for (Element e : elements) { doSomething(e); }
。”请注意,没有性能损失 使用e
循环,甚至是数组。事实上,它可能会提供轻微的 在某些情况下,优于普通for-each
循环的性能优势 只计算一次数组索引的限制。
答案 4 :(得分:1)
由于没有解释有效性的确切含义,我将从应该优先考虑的位置回答。
我希望第二种方法是最具可读性的。
为什么:在编写程序之前,你不知道瓶颈会在哪里。这就是为什么过早优化不太可能产生实质性结果的原因。但是代码的可读性总是相关的,所以我会选择第二种变体。
答案 5 :(得分:1)
如果您关注性能,则取决于List的实现。
使用ArrayList
,第一个是最快的。它和以下一样快:
for(int i = 0, n=list.size(); i < n; i++) {
System.out.println(list.get(i));
}
那是因为ArrayList
缓存了一个数组。当您致电objArrayList.get(i)
时,它会返回objArrayList.buffArray[i]
。
对于每个和Iterator是相同的。因此同样缓慢
答案 6 :(得分:0)
我更喜欢方法2,除非你需要在迭代器对象上调用一些东西(比如iterator.remove()
),在这种情况下我更喜欢方法3.
我将使用方法1的唯一情况是,无论如何我需要在循环中保持索引的跟踪(然后仅在 List
的情况下是密集的数组实施,如ArrayList
)。
<强>更新强>
与评论/其他答案一致,我现在更喜欢ListIterator
在迭代List跟踪索引时。使方法1仅对通过任何其他方法无法实现的方法有用。
答案 7 :(得分:0)
我发现this比较或许可以帮助你转发
答案 8 :(得分:0)
许多因素都会影响这项措施。
首先,请注意您的第二个和第三个备选方案是字节码等效的。实际上,Java enhanced for loop是一个语法糖,这意味着,在你的情况下,第二种选择将在编译后看起来像完全,就像你的第三种选择。
最明显的一个是所选择的实施。如果使用LinkedList
而不是ArrayList
进行此测试,您会发现显然迭代器比随机数组访问更快。在某些情况下,您甚至会发现迭代LinkedList
比迭代ArrayList
更快。现在你怎么做?在你的大部分代码中,你都会毫无理由地从一个代码转换到另一个代价。
因此,作为一般经验法则,对于可读性和代码效率,我倾向于总是使用第二种方法。