我想知道是否有更好的(或者只是其他)方法来获取进入流的终端操作而不是以下内容的所有项目的计数:
Stream<T> stream = ... // given as parameter
AtomicLong count = new AtomicLong();
stream.filter(...).map(...)
.peek(t -> count.incrementAndGet())
其中count.get()
给出了该阶段处理项目的实际数量。
我故意跳过终端操作,因为这可能会在.forEach
,.reduce
或.collect
之间发生变化。
我确实知道.count
,但只有当我将.forEach
与.map
交换并使用.count
作为终端操作时,它似乎才能正常工作。但在我看来好像.map
被误用了。
我不喜欢上述解决方案:如果在其后添加过滤器,它只计算该特定阶段的元素,而不是进入终端操作的元素。
我想到的另一种方法是将已过滤和映射的值collect
放入列表并对其进行操作,然后调用list.size()
来获取计数。但是这不起作用,如果流的集合会导致错误,而使用上述解决方案,我可以计算到目前为止所有已处理的项目,如果适当的try/catch
到位。然而,这并不是一项艰难的要求。
答案 0 :(得分:6)
在终端操作IMO之前,您似乎已经通过peek
获得了最干净的解决方案。我认为这是需要的唯一原因是出于调试目的 - 如果是这样的话,那么peek
就是为此而设计的。为此包装流并提供单独的实现是太多了 - 除了大量的时间和以后支持所有被添加到Streams
的内容。
对于的部分,如果添加了另一个过滤器怎么办?那么,提供一个代码注释(我们很多人这样做)和一些测试用例,否则会失败,例如。
只是我的0.02美元
答案 1 :(得分:0)
最好的想法是使用自身的映射,同时计算映射例程的调用。
steam.map(object -> {counter.incrementAndGet(); return object;});
由于这个lambda可以重用,你可以用对象替换任何lambda,你可以创建一个这样的计数器对象:
class StreamCounter<T> implements Function<? super T,? extends T> {
int counter = 0;
public T apply(T object) { counter++; return object;}
public int get() { return counter;}
}
所以使用:
StreamCounter<String> myCounter = new ...;
stream.map(myCounter)...
int count = myCounter.get();
由于地图调用再次只是另一个重用点,因此可以通过扩展Stream并包装普通流来提供map方法。
这样你可以创建类似的东西:
AtomicLong myValue = new AtomicLong();
...
convert(stream).measure(myValue).map(...).measure(mySecondValue).filter(...).measure(myThirdValue).toList(...);
通过这种方式,您可以简单地拥有自己的Stream包装器,该包装器在其自己的版本中透明地包装每个流(这不是性能或内存开销)并测量任何此类测量点的基数。
这通常在创建map / reduce解决方案时分析算法的复杂性时完成。通过不使用原子长实例进行计数来扩展流实现,但只有流量实现的度量点名称可以包含无限数量的度量点,同时提供灵活的打印报告方式。
这样的实现可以记住流方法的具体顺序以及每个测量点的位置,并带来如下输出:
list -> (32k)map -> (32k)filter -> (5k)map -> avg().
此类流实现只编写一次,可用于测试,也可用于报告。
内置于每天的实现中,可以收集某些处理的统计信息,并通过使用不同的操作排列来实现动态优化。例如,这将是一个查询优化器。
因此,在您的情况下,最好的方法是首先重用StreamCounter
,并且根据使用频率,计数器的数量和DRY原则的亲和力最终会在以后实施更复杂的解决方案。
PS:StreamCounter
使用int值并且不是线程安全的,因此在并行流设置中,可以用int
实例替换AtomicInteger
。