在我的程序中,我反复 1 collect Java 8 streams将对象集合减少为单个对象。在整个执行过程中,此集合的大小可能会有很大差异:从3个对象到数百个。
public void findInterestingFoo(Stream<Foo> foos) {
internalState.update(foos.collect(customCollector()));
}
在优化代码和搜索瓶颈的过程中,我在某个时刻制作了流parallel。这在当时很有效,因为收藏品都很大。之后,在更改程序的其他部分和参数后,集合变得更小。我意识到不使流并行更有效率。这是有道理的:在4个对象的多个线程上分配工作的开销根本不值得。但 对于数百个对象来说是值得的。
如果我只能使大流并行,那将非常方便:
public void findInterestingFoo(Stream<Foo> foos) {
if (isSmall(foos)) {
internalState.update(foos.collect(customCollector()));
} else {
internalState.update(foos.parallel().collect(customCollector()));
}
}
当然,在从an array,a collection或manually创建流时,可以手动执行此操作。也就是说,我们知道流中有哪些元素,因此可以对其进行跟踪。然而,我有兴趣以通用的方式解决这个问题,因此无论将哪种类型的流传递给findInterestingFoo
,都会尽可能有效地处理它。
像count()
之类的内容可能会有所帮助,除非它在collect之前终止了流。
我很清楚流的设计没有设定的大小,特别是:
- 可能没有界限。虽然集合的大小有限,但流不需要。诸如
limit(n)
或findFirst()
之类的短路操作可以允许无限流上的计算在有限时间内完成。 -java.util.stream
package description
不过,我想知道是否有任何方法可以确定在对执行任何操作之前中有多少元素。流是否真的不知道它是从有限集合创建的?
__________
1 成千上万次。在我的情况下,优化这一点可以使总运行时间从1.5到0.5秒加速。
答案 0 :(得分:16)
理论上,你可以这样做:
public void findInterestingFoo(Stream<Foo> foos) {
Spliterator<Foo> sp = foos.spliterator();
long size = sp.getExactSizeIfKnown();// returns -1 if not known
// or sp.estimateSize(); // Long.MAX_VALUE means "unknown"
internalState.update(
StreamSupport.stream(sp, size > PARALLEL_THRESHOLD)
.collect(customCollector()));
}
spliterator()
是一个使用输入流的终端操作,但您可以将Spliterator
传递给StreamSupport.stream
以构建具有完全相同属性的流。第二个参数已经告诉流是否应该是并行的。
理论上。
实际上,当前流实现将返回不同的Spliterator
实现,具体取决于流是否并行。这意味着在调用spliterator()
之前,当原始流尚未并行时,将流重新创建为并行流可能会导致无法执行并行处理的流。
但是,如果没有中间操作,例如,它可以正常工作。当您直接传入从集合或数组创建的Stream
时。
在spliterator()
之前调用parallel()
以获得可能仍然按顺序运行的并行功能流,如果您决定这样做,则可以在很多情况下工作。但是,如果输入流中存在类似sorted()
的有状态中间操作,则它们可能会被修复为并行运行,即使您按顺序执行collect
(反之亦然)。
另一个问题是基本性质。元素的数量实际上并没有说明并行处理是否会带来好处。这取决于每个元素的工作负载,它不仅取决于您的终端collect
操作,还取决于在输入方法之前已链接到流的操作。即使您得出结论,收集器的工作负载已经足够高,值得进行并行处理,但可能是传入流具有skip
,limit
或distinct
等操作(在有序流上) ,它经常并行运行更糟,并且需要一个完全不同的阈值。
一个更简单的解决方案是让调用者决定,因为调用者知道流的大小和性质。您甚至不需要为方法的签名添加选项,因为调用者可以通过在将流传递给您的方法之前调用流上的parallel()
或sequential()
来做出决定,并且您可以尊重只是不改变模式。