每日一例 | forEach、forItem和forI的性能测试
前言
日常开发中,for循环是我们最常用的循环之一,我们经常用他们来遍历各种集合,除了forI这种最基本的for循环,还有forItem这种增强的for循环,而且随着lambda表达式的诞生,jdk还为我们提供了另外一种循环遍历方式——forEach,但是我一直有个疑问,到底他们的性能如何呢,实际开发中我们应该如何选择呢?今天,我们就来探讨下这个问题。
性能测试
准备
开始之前,我们先写一段测试代码,这段代码的作用就是构建一个String的List,然后我们分别用他们三个进行循环遍历,分别计算用时
public static void main(String[] args) {
int initSize = 100;
List<String> stringList = new ArrayList<>(initSize);
for (int i = 0; i < initSize; i++) {
stringList.add(String.format("%d%s", i, "str"));
}
// forEach
long startTimeForEach = System.currentTimeMillis();
stringList.forEach(System.out::println);
long endTImeForEach = System.currentTimeMillis();
// for item
long startTimeForItem= System.currentTimeMillis();
for (String s : stringList) {
System.out.println(s);
}
long endTimeForItem= System.currentTimeMillis();
// for i
long startTimeForI= System.currentTimeMillis();
for (int i = 0; i < stringList.size(); i++) {
System.out.println(stringList.get(i));
}
long endTimeForI= System.currentTimeMillis();
System.out.println(String.format("List为%d时,forEach遍历用时:%d", initSize, (endTImeForEach - startTimeForEach)));
System.out.println(String.format("List为%d时,forItem遍历用时:%d", initSize, (endTimeForItem - startTimeForItem)));
System.out.println(String.format("List为%d时,forI遍历用时:%d", initSize, (endTimeForI - startTimeForI)));
}
开始测试
初始值为100
我们先把List的初始化大小设为100,测试的结果表明,在此等数据量下,forI和forItem的用时基本一致,多次运行,偶尔,forI表现会更好一点,但是forEach的时间性能就没那么优秀了,和前两都有着数量级的区别:

初始值为1,000
这时候,forI和forItem的差距就体现出来了,后者比前者快了近一倍,当然,forEach性能依然表现最差。和100时相比,forI用时翻了6倍,forItem用时翻了5倍,forEach用时翻了不到两倍。

重复多次运行,forItem和forI有差距,稳定在20左右,但不是很大,forEach稳定在200左右

初始值10,000
这时候的性能表现,竟然是forI最优,当然依然是forEach最差

多次运行也基本维持这个结论

初始值100,000
数据量达到十万的时候,forEach才慢慢开始有优势了,但是还是超不过forI

偶尔会有和结论相悖的结果的,但这种情况很少

大部分都是符合我们结论的

LinkedList
但如果把ArrayList改成LinkedList,数据量超过10,000基本上都是forI表现最差
100,000时

10,000

1000
这里还是forEach最差

100
forI最好

总结
上面的结论已然很直观了,综合表现forI最佳,forItem其次,最差的是forEach,但并不是说以后开发就只推荐你用forI,其实还是要看你具体业务的,如果你要用lambda表达式,那forEach就是最好的选择,你也没得选。当然,以上测试结果可能和环境也有关系(JDK1.8),毕竟12年的老爷机了,要啥自行车……
