我尝试实现一个具有以下签名的方法:
```
public static <A,B> Pair<Stream<A>, Stream<B>> flatten(Iterator<Pair<Stream<A>, Stream<B>>> iterator);
该方法的目标是将每个流类型压平为单个流并将输出包装在一对中。我只有一个Iterator(而不是Iterable),并且不能更改方法签名,因此必须在单次迭代中执行压平操作。
我当前最好的实现是
public static <A,B> Pair<Stream<A>, Stream<B>> flatten(Iterator<Pair<Stream<A>, Stream<B>> iterator) {
Stream<A> aStream = Stream.empty();
Stream<B> bStream = Stream.empty();
while(iterator.hasNext()) {
Pair<Stream<A>, Stream<B>> elm = iterator.next();
aStream = Stream.concat(aStream, elm.first);
bStream = Stream.concat(bStream, elm.second);
}
return Pair.of(aStream, bStream);
}
但虽然这在技术上是正确的,但我有两个原因不太满意:
- Stream.concat 警告不要做这种事情,因为它可能会导致 StackOverflowError。
- 从风格上讲,如果可能的话,我宁愿它是纯函数式的,而不是不得不循环遍历迭代器并重新分配流。
感觉 Stream#flatMap 在这里应该很合适(在使用 Guava's Streams.stream(Iterator) 将输入 Iterator 转换为 Stream 后),但似乎由于中间的 Pair 类型而无法工作。
另一个要求是任何迭代器/流都可能非常大(输入可能包含从一对非常大的流到许多单项流的任何位置),因此解决方案理想情况下不应将结果收集到内存中的集合中。
iterator.forEachRemaining()
来替换StreamSupport.stream(…).forEach()
。 - Didier LList
),但它是非公共的SpinnedBuffer
。 - EugeneArrayList
稍微更有效率,因为它不需要在增加数组容量操作时进行数据复制。 - HolgerArrayList
更有效率,但我没有看过代码,所以我无法确定我是否会有时间去研究它。感谢您至少给了一些提示。 - EugeneArrayList
有一个一维数组,所以当其容量用尽时,将分配一个新的更大的数组并复制内容。SpinedBuffer
有一个数组的数组,最初未填充。当当前目标数组已满时,它将存储在数组的数组中,并分配一个新的更大的数组作为新的目标数组,但不进行复制。这种设计会使插入或删除变得复杂,但这些操作也不受支持。但是,在这个已知大小的二维数组上进行流处理仍然很快。 - Holger