我大都同意@Holger的出色回答,但我会不同地强调口音。我认为您很难理解对缓冲区的需求,因为您对Stream
API所允许的思维模型非常简单。如果一个人想着流中的序列
map和
filter,也没有必要额外的缓冲,因为这些行动有2个重要的“好”的属性:
- 一次处理一个元素
- 结果产生0或1个元素
但是,在一般情况下,这些都不是正确的。正如@Holger所提到的
flatMap,Java
8中已经打破了规则2,在Java9中,他们终于添加了takeWhile,它实际上是在整个
Stream->
Stream而不是在每个元素的基础上进行转换的(并且这是AFAIK的第一个中间衬衫循环操作)。
我不太同意@Holger的另一点是,我认为最根本的原因与他在第二段中提到的原因(即a)有些不同,您可能会称其
tryAdvance为
Stream多次结束,而b)“ 没有保证呼叫者将始终通过相同的消费者 ”)。我认为最重要的原因是,
Spliterator
在功能上必须相同,
Stream必须支持短路和惰性(即不处理全部内容
Stream或不支持未绑定流的能力)。换句话说,即使Spliterator
API(相当奇怪)要求您必须使用同一
Consumer对象的所有方法的所有调用一个给定的
Spliterator,你仍然需要
tryAdvance和
tryAdvance实现仍然必须使用一些缓冲区。如果您所拥有的只是全部,就无法停止处理数据,
forEachRemaining(Consumer<?super T>)因此您将无法实现与之相似
findFirst或
takeWhile使用的任何数据。实际上,这是JDK实现内部使用
Sink接口而不是接口
Consumer(以及“换行”
wrapAndCopyInto代表的意思)的原因之一:
Sink具有其他
boolean cancellationRequested()方法。
所以 总结一下 :需要一个缓冲,因为我们希望
Spliterator:
- 使用简单方法
Consumer
,它不提供报告处理/取消后端的方法 - 提供一种手段,可以根据(逻辑)使用者的请求停止处理数据。
请注意,这两个要求实际上有点矛盾。
示例和一些代码
在这里,我想提供一些代码示例,我认为如果没有当前的API协定(接口),没有附加缓冲区就无法实现。本示例基于您的示例。
有一个简单的整数整数Collatz序列被猜想总是最终命中1。AFAIK该猜想尚未得到证明,但已针对许多整数(至少对于整个32位int范围)进行了验证。
因此,假设我们要解决的问题如下:从Collatz序列流中的1到1,000,000范围内的随机起始数字中查找第一个以十进制表示形式包含“ 123”的数字。
这是一个仅使用
Stream(不是
Spliterator)的解决方案:
static String findGoodNumber() { return new Random() .ints(1, 1_000_000) // unbound! .flatMap(nr -> collatzSequence(nr)) .mapToObj(Integer::toString) .filter(s -> s.contains("123")) .findFirst().get();}这里
collatzSequence是一个返回
Stream包含Collatz序列直到第一个1
的函数(对于nitpickers,请让它在当前值大于时也停止,
Integer.MAX_VALUE /3这样我们就不会溢出)。
每一个这样
Stream的返回
collatzSequence势必。标准
Random也会最终生成所提供范围内的每个数字。这意味着我们可以保证流中最终会有一些“好”的数字(例如
123),并且
findFirst存在短路,因此整个操作实际上将终止。但是,没有合理的Stream
API实现可以预测这一点。
现在假设由于某些奇怪的原因,您想使用middle来执行相同的操作
Spliterator。即使您只有一个逻辑并且不需要不同
Consumer的,也不能使用
forEachRemaining。因此,您必须执行以下操作:
static Spliterator<String> createCollatzRandomSpliterator() { return new Random() .ints(1, 1_000_000) // unbound! .flatMap(nr -> collatzSequence(nr)) .mapToObj(Integer::toString) .spliterator();}static String findGoodNumberWithSpliterator() { Spliterator<String> source = createCollatzRandomSpliterator(); String[] res = new String[1]; // work around for "final" closure restriction while (source.tryAdvance(s -> { if (s.contains("123")) { res[0] = s; } })) { if (res[0] != null) return res[0]; } throw new IllegalStateException("Impossible");}同样重要的是,对于某些起始数字,Collatz序列将包含多个匹配数字。例如,
41123和
123370(== 41123 * 3 + 1)都包含“
123”。这意味着我们真的不希望
Consumer在第一个匹配命中后被调用。但是由于
Consumer没有公开报告处理结束的任何方法,
WrappingSpliterator所以不能仅仅将我们传递
Consumer给内部
Spliterator。唯一的解决方案是将内部的所有结果
flatMap(以及所有后处理)累积到某个缓冲区中,然后一次在该缓冲区上迭代一个元素。



