0

假设我们有一段代码,例如:

Arrays.stream(queries)
  .limit(queries.length - 1).mapToInt(i -> i)
  .sum();

查询是一个由 N 个整数组成的数组来澄清问题,假设它是 100 万个整数,因此该数组将占用 ~4MB(每个整数 1M * 4 个字节)。

流会占用相当大的空间吗?或者我们是否会使用大约 4MB 并通过数组进行流式传输,而无需重新分配整个数组来运行以下代码(不考虑运行 JVM 所需的空间)。

4

1 回答 1

3

答案是:

[A] 一个实现细节。java规范根本不会告诉你,因此任何确切的答案都需要用'.. on this hardware, this OS, this VM impl, this version, 在这些情况下'。然而...

[B] 不管答案是什么,它既是“相当快/空间不大”,也绝对是“不依赖于 N 的值”。

选择流中的“流”不是为了好玩:流 API 实际上是。它不需要整个数组,然后创建一个包含所有值的新对象以准备流式传输,然后再limit创建另一个新的巨型数组(小一号),然后再mapToInt创建另一个。这不是它的工作原理。

流是一个管道。sum在您运行终端命令(是终端)之前,什么都不会发生。你可以检查这个:

Arrays.stream(queries).mapToInt(i -> {
  System.out.println(i);
  return i.intValue();
});

这不会打印任何东西。完全没有。因为这只是一个半生不熟的流过程,没有终端,它不会“流动”。

如果您在上面调用 sum ,则打印开始发生。具体来说,终端(sum()此处为 )开始“从流中提取值”。这向上传播。sum 要求mapToInt一个值,为此,mapToInt要求limit一个值(然后将获取该值,将其滚动通过i -> ilambda,并将其提供给sum)。limit然后将要求Arrays.stream一个值,然后实际从数组中读取单个项目。涉及到中间跟踪器对象,但它们的大小不依赖于 N。例如,返回的对象Arrays.stream(queries)持有对queries数组的引用(无论该数组有多大,大约有 64 位数据;只是一个指针),并且一个知道我们在哪里的 int 值1

代表limit它的一部分的对象只有一个int跟踪到目前为止已经提供了多少值的对象。当它从中提取的东西用完或已经提供了物品limit时,就像没有更多的值可以提供一样,以较早发生者为准。 limit

等等。因此,这些跟踪器对象到底有多大是一个实现细节,但是,它们是“小”的(至少,相对于百万整数数组!),并且不依赖于流的大小。事实上,实际上可以存在无限的流,没有问题。他们确实这样做了 - 检查其Stream自身的 API,例如,您可以在其中轻松创建一个返回无限量值的流1

[1] 我过于简单化了。流还具有根据少数情况可以并行化的特性。当您涉及计数器时,并行化变得非常困难,因此这些跟踪器有点复杂。如果您想要完整的详细信息,请查看SpliteratorStreamUtils. 但是,这种过于简单的解释足以理解没有多少流中间操作会使您面临内存不足的风险。

于 2021-09-18T17:57:30.230 回答