有人可以向我解释为什么下面的代码在流版本中比在一个版本中更快,而不仅仅是返回主列表的子列表的过滤器?为什么要过滤一个流比仅仅返回一个子列表的迭代代码更快?
public static final int N = 50000000;
static List<Integer> sourceList = new ArrayList<>();
static {
for (int i = 0; i < N; i++) {
sourceList.add(i);
}
}
@Benchmark
public List<Pair<Integer, Integer>> vanilla() {
List<Pair<Integer, Integer>> collect1 =
sourceList.stream()
.map(integer -> Pair.of(integer, integer))
.collect(Collectors.toList());
return collect1.subList(1000, 100000);
}
@Benchmark
public List<Pair<Integer, Integer>> stream() {
return sourceList.stream()
.map(value -> Pair.of(value, value))
.filter(value -> value.getLeft() > 1000 && value.getLeft() < 100000)
.collect(Collectors.toList());
}
Benchmark Mode Cnt Score Error Units
Test.stream avgt 20 9.867 ± 0.218 ns/op
Test.vanilla avgt 20 183.304 ± 8.550 ns/op
我使用JMH运行测试,我不明白结果。我想通过添加将Integer值封装在Pair中的映射函数会强制流创建所有新对象,以将它们传递给过滤器方法,然后提取Pair的左边部分进行比较。对于我来说,这听起来比另一种没有过滤的方法更加密集,结果只是原来的一个子列表,因此没有遍历整个列表。
我失去了一些东西在这里?
好的,这是有道理的。我知道先过滤会更快,但我试图简化此示例的生产代码,该代码也首先进行一些映射。生产代码的确如此,它必须按照它们来的顺序为每个对象分配一个int值,然后只保留那些给定下限和上限的值。问题是我需要保持一个计数器,并将其传递给过滤器,因为对象不是像这样的int。是否有任何方法可以重构此示例代码以保持语义,同时反转映射和过滤功能? – Kilian
在你发布的例子中首先进行过滤将是微不足道的,对吗?对于您的实际生产代码,我不能没有看到它。 –