Java stream 和 for循环效率对比问题

针对同一个集合,用 stream 操作两次得到两个不同条件筛选出来的集合和map,和一次for循环就搞定搞定的效率对比。

虽然stream写起来链式操作很舒服,但效率在不同数据量下的体现效果是不一样的,以下为我的测试代码:

    @Test
public void testStreamAndFor() {
List<Student> studentList = new ArrayList<>();
// 初始数据量
int listSize = 100000;
// 测试次数,以便求出平均运行时长
int testTimes = 5;
for (int i = 0; i < listSize; i++) {
Student student = new Student();
student.setId(i + 1);
student.setStudentName("name" + i);
student.setAge(i);
studentList.add(student);
}
BigDecimal streamTotalRunTime = new BigDecimal("0");
BigDecimal forTotalRunTime = new BigDecimal("0");
for (int i = 0; i < testTimes; i++) {
Instant streamStart = Instant.now();
Map<Long, Student> idMapOfStream = studentList.stream()
.collect(Collectors.toMap(Student::getId, v -> v));
List<Integer> studentAgeListOfStream = studentList.stream()
.map(Student::getAge)
.collect(Collectors.toList());
long streamRunTime = Duration.between(streamStart, Instant.now()).toMillis();
System.out.println("第" + (i + 1) + "次:" + "stream 耗时:" + streamRunTime);
Instant forStart = Instant.now();
int size = studentList.size();
Map<Long, Student> idMapOfFor = new HashMap<>(size);
List<Integer> ageListOfFor = new ArrayList<>();
for (Student student : studentList) {
idMapOfFor.put(student.getId(), student);
ageListOfFor.add(student.getAge());
}
long forRunTime = Duration.between(forStart, Instant.now()).toMillis();
System.out.println("第" + (i + 1) + "次:" + "for 耗时:" + forRunTime);
streamTotalRunTime = streamTotalRunTime.add(new BigDecimal(streamRunTime + ""));
forTotalRunTime = forTotalRunTime.add(new BigDecimal(forRunTime + ""));
}
System.out.println("list长度为:" + listSize + ", 总共测试次数:" + testTimes);
System.out.println("stream总运行时间(ms) :" + streamTotalRunTime);
System.out.println("for总运行时间(ms) :" + forTotalRunTime);
BigDecimal streamAverageRunTime = streamTotalRunTime.divide(new BigDecimal(testTimes + ""), 2, BigDecimal.ROUND_HALF_UP);
System.out.println("stream平均每次运行时间(ms) :" + streamAverageRunTime);
BigDecimal forAverageRunTime = forTotalRunTime.divide(new BigDecimal(testTimes + ""), 2, BigDecimal.ROUND_HALF_UP);
System.out.println("for平均每次运行时间(ms) :" + forAverageRunTime);
}

当数据量为10w,测试5次的结果输出:

第1次:stream 耗时:81
第1次:for 耗时:13
第2次:stream 耗时:15
第2次:for 耗时:23
第3次:stream 耗时:7
第3次:for 耗时:11
第4次:stream 耗时:7
第4次:for 耗时:13
第5次:stream 耗时:9
第5次:for 耗时:6
list长度为:100000, 总共测试次数:5
stream总运行时间(ms) :119
for总运行时间(ms) :66
stream平均每次运行时间(ms) :23.80
for平均每次运行时间(ms) :13.20

当数据量为100w,测试5次的输出结果:

第1次:stream 耗时:165
第1次:for 耗时:1296
第2次:stream 耗时:447
第2次:for 耗时:62
第3次:stream 耗时:363
第3次:for 耗时:359
第4次:stream 耗时:61
第4次:for 耗时:350
第5次:stream 耗时:389
第5次:for 耗时:43
list长度为:1000000, 总共测试次数:5
stream总运行时间(ms) :1425
for总运行时间(ms) :2110
stream平均每次运行时间(ms) :285.00
for平均每次运行时间(ms) :422.00

所有运行时长单位均为ms。综上测试结果,当数据量少于百万级别的,一次for循环来筛选数据效率更高,当数据量达到八万级别,还是使用stream来操作更加具有效率。但是小弟还是有点不明白原因是为何,求高人指点一二

回答

其实用 stream 主要是很多写起来代码比较简洁,如果数据量大的话,还能很容易转换为并行流,帮你做了多线程,不需要自己代码去写多线程了

感谢老哥:+1:

我用Java11运行100W数据量的结果是二者性能差别不大, 如果堆调大一些的话, for性能更好, 说明stream需要更多的内存分配

这。。我还是用的万年java8~~老哥的这种方式,可以借鉴一手

你这代码,确定 for 和 stream 干的是同样的事情? Student::getAge 也发下看看

回复 @是码农 : 我觉得你的结果很有问题,所以才需要你发出来看下,如果不发那就算了,我对你的结果存疑回复 @乌龟 : 代码一直都是如我所说那样操作的回复 @是码农 : 你可以把代码补充到问题里,光说意义不大,很多性能问题都是细节上的问题for循环和stream都是将Student::getId放入新的map中(id为key,Student对象为value),以及将Student::getAge放入新的List中。两种方式都是对相同字段做的相同操作。

语义和使用便捷性,是优先考虑。大多数的性能问题,都不在这些suger

对于数据量和操作方式来讲,应该是有最优解,在性能差异不大的情况下肯定选择语义和使用便捷性,但性能差距过大,还是性能优先。就测试demo来看,stream筛选数据到新的数据结构(两种或多种)中,需要使用stream操作两次或多次,但使用for的话只需遍历一次就可以将原数据中所有想要的数据提取出来,所以这种情况下,是否应该要考虑for来提高效率呢

数值变化差值也太大了吧????????第一次for1200+  第五次43?

我又再一次测试了下确实如此,但我将最外层遍历(测试次数)取消掉,通过手动运行来增加测试次数后,每一次运行时长都是几十毫秒,差值并不大。所以应该是最外层遍历导致这样的差值,但这只是表面上的原因,底层原因就有点迷惑了

这测试的太草率了。

确实有一点粗糙。。大佬可有好的建议:smile:

  1. for循环里把map的size直接就给定了,扩容要比stream少
  2. 在for循环里干了两件事,stream调用了两次
  3. 数据在10w级别,第一次调用for确实要比stream快,后续来看两者有持平的趋势
  4. 两次环境同时执行,运行环境状态不可控

可以分多次、时间线更长一点,多线程条件下在对比执行(模拟真实的应用场景)

不用测,stream内部就是for循环

不要把Stream 当做是循环,这2个并不是一回事,Stream是流,更像是一个管子,循环只是循环

就题主测试的场景来说,性能上必然是循环更快的。

Stream并不是一个循环,而是一个连接上下游的管子,比如说,我们使用Files.lines  来按行遍历一个文件的时候,他并不是一次性把文件加载到内存,解析成一个按行的集合,再进行遍历的,而是基于迭代器,每读取解析完一行数据,就发送到Stream流里,下游,map也好,flatMap也好forEach也好等等,都是每当上游发送一个数据,就处理一个数据,因为是流式的,看起来就跟遍历一起,其实这2个并不一样

那么Stream为什么能实现集合遍历呢?跟上边说的文件遍历性质是一样的,就是直接借助集合的迭代器,把集合的数据一个个发送到流里,然后下游处理,形成了看是遍历的操作,Stream处理要完成消息的传递,里面还要有其他很多辅助东西的处理,比如上游消息已经全发完了得通知到下游吧。从而就遍历上来说,Stream的性能是不可能比循环更快的

看下并行流的100W的结果,不要只是拿顺序流来测试

以上是Java stream 和 for循环效率对比问题的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>