当前位置: 首页 > 面试题库 >

为什么stream.spliterator()的tryAdvance会将项目累积到缓冲区中?

司空叶五
2023-03-14
问题内容

SpliteratorStream管道获取可能会返回StreamSpliterators.WrappingSpliterator的实例。例如,获取以下内容Spliterator

Spliterator<String> source = new Random()
            .ints(11, 0, 7) // size, origin, bound
            .filter(nr -> nr % 2 != 0)
            .mapToObj(Integer::toString)
            .spliterator();

鉴于以上所述Spliterator<String> source,当我们通过的tryAdvance (Consumer<? super P_OUT> consumer)方法(Spliterator在本例中为StreamSpliterators.WrappingSpliterator的一个实例)单独遍历元素时,它将首先将项目累积到内部缓冲区中,然后再使用这些项目,如我们在StreamSpliterators.java中所看到的那样。
#298
。从简单的角度来看,doAdvance()先将项目插入buffer,然后再获取下一个项目并将其传递给consumer.accept (…)

public boolean tryAdvance(Consumer<? super P_OUT> consumer) {
    boolean hasNext = doAdvance();
    if (hasNext)
        consumer.accept(buffer.get(nextToConsume));
    return hasNext;
}

但是,我没有弄清楚这一点的必要性buffer

在这种情况下,为什么不将的consumer参数tryAdvance简单地用作Sink管道的终端?


问题答案:

我大都同意@Holger的出色回答,但我会不同地强调口音。我认为您很难理解对缓冲区的需求,因为您对Stream
API所允许的思维模型非常简单。如果一个人想着流中的序列mapfilter,也没有必要额外的缓冲,因为这些行动有2个重要的“好”的属性:

  1. 一次处理一个元素
  2. 结果产生0或1个元素

但是,在一般情况下,这些都不是正确的。正如@Holger所提到的flatMap,Java
8中已经打破了规则2,在Java9中,他们终于添加了takeWhile,它实际上是在整个Stream->
Stream而不是在每个元素的基础上进行转换的(并且这是AFAIK的第一个中间衬衫循环操作)。

我不太同意@Holger的另一点是,我认为最根本的原因与他在第二段中提到的原因(即a)有些不同,您可能会称其tryAdvanceStream多次结束,而b)“ 没有保证呼叫者将始终通过相同的消费者 ”)。我认为最重要的原因是,Spliterator
在功能上必须相同,Stream必须支持短路和惰性(即不处理全部内容Stream或不支持未绑定流的能力)。换句话说,即使Spliterator
API(相当奇怪)要求您必须使用同一Consumer对象的所有方法的所有调用一个给定的Spliterator,你仍然需要tryAdvancetryAdvance实现仍然必须使用一些缓冲区。如果您所拥有的只是全部,就无法停止处理数据,forEachRemaining(Consumer<? super T> )因此您将无法实现与之相似findFirsttakeWhile使用的任何数据。实际上,这是JDK实现内部使用Sink接口而不是接口Consumer(以及“换行”
wrapAndCopyInto代表的意思)的原因之一:Sink具有其他boolean cancellationRequested()方法。

所以 总结一下 :需要一个缓冲,因为我们希望Spliterator

  1. 使用简单方法Consumer,它不提供报告处理/取消后端的方法
  2. 提供一种手段,可以根据(逻辑)使用者的请求停止处理数据。

请注意,这两个要求实际上有点矛盾。

示例和一些代码

在这里,我想提供一些代码示例,我认为如果没有当前的API协定(接口),没有附加缓冲区就无法实现。本示例基于您的示例。

有一个简单的整数整数Collat​​z序列被猜想总是最终命中1。AFAIK该猜想尚未得到证明,但已针对许多整数(至少对于整个32位int范围)进行了验证。

因此,假设我们要解决的问题如下:从Collat​​z序列流中的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包含Collat​​z序列直到第一个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");
}

同样重要的是,对于某些起始数字,Collat​​z序列将包含多个匹配数字。例如,41123123370(== 41123 * 3 + 1)都包含“
123”。这意味着我们真的不希望Consumer在第一个匹配命中后被调用。但是由于Consumer没有公开报告处理结束的任何方法,WrappingSpliterator所以不能仅仅将我们传递Consumer给内部Spliterator。唯一的解决方案是将内部的所有结果flatMap(以及所有后处理)累积到某个缓冲区中,然后一次在该缓冲区上迭代一个元素。



 类似资料:
  • 在zlib的deflate实现中,注意到当给定的数据长度较小时,zlib函数会将数据复制到输入缓冲区,只有当输入缓冲区满时才开始压缩。 当给定的数据长度大于输入缓冲区大小时,zlib函数直接开始压缩。 我想知道将短数据积累到输入缓冲区中有什么好处。我能想到的是: 避免花费在块处理上的开销,包括Huffman树初始化和CRC caculation 保持I/O工作在8KB(或更大)块上,有利于提高性能

  • 我们正在迁移到Spring WebFlux(带有Reactornetty)。应用程序使用HTTP协议和Spring控制器。目前,我们有一种过渡解决方案,它将入站IO缓冲区累积到合成字节buf中,而不进行复制(然后将其作为输入流进行处理)。Reactornetty为我们提供直接字节缓冲区。因此,为这些缓冲区调用是至关重要的。最初我们有代码: 在处理得到的复合缓冲液后进行释放。 但当上游发布器发出错误

  • 我读到FileWriter和BufferedWriter的区别在于FileWriter直接写入文件(逐字符),white BufferedReader使用缓冲区。如果是,为什么FileWriter有缓冲区?例如,如果我创建一个FileWriter对象,如下所示: 而且,如果我在程序结束时不刷新或关闭写入器,它将不会向文件写入任何内容。这意味着它也使用缓冲区。拜托,解释一下?

  • 两者都是序列化库,由谷歌开发人员开发。他们之间有什么大的区别吗?将使用协议缓冲区的代码转换为使用FlatBuffers需要大量工作吗?

  • 问题内容: python中有一个类型,但是我不知道该如何使用它。 在Python文档中,描述为: object参数必须是支持缓冲区调用接口的对象(例如字符串,数组和缓冲区)。将创建一个引用该对象参数的新缓冲区对象。缓冲区对象将从对象的开头(或指定的偏移量)开始是一个切片。切片将延伸到对象的末尾(或具有由size参数指定的长度)。 问题答案: 用法示例: 在这种情况下,缓冲区是一个子字符串,从位置6

  • 我有一个JSON对象,我正在将它转换成一个,并在这里进行一些处理。稍后,我想将相同的缓冲区数据转换为有效的JSON对象。 我的工作节点V6.9.1 下面是我尝试过的代码,但当我转换回JSON并且无法打开此对象时,我得到了。 所以我试着用检查的方式打印整个物体 如果我试着像数组一样读取它 我试图解析它也抛出SynTaxError:意外令牌o在JSON在位置2 我需要像我创建的那样将其视为真实对象(我