假设我们有一段代码,例如:
Arrays.stream(queries)
.limit(queries.length - 1).mapToInt(i -> i)
.sum();
其中querys是一个由N个整数组成的数组。 为了澄清这个问题,假设它有 100 万个整数,因此该数组将占用 ~4MB(每个整数 1M * 4 字节)。
流会占用相当大的空间吗?或者我们是否会使用大约 4MB 并流式传输数组,而无需重新分配整个数组来运行以下代码(不考虑运行 JVM 所需的空间)?
答案是:
[A] 实施细节。 java 规范根本不会告诉你,因此任何确切的答案都需要用“.. 在这个硬件、这个操作系统、这个 VM impl、这个版本,在这些情况下”来说明。然而...
[B] 无论答案是什么,它都是“相当快/空间不大”,而且绝对“不依赖于 N 的值”。
stream 中的“Stream”并不是为了好玩而选择的:事实上,stream API 确实是stream。它不会获取整个数组,然后创建一个包含准备好流式传输的所有值的新对象,然后
limit
制作另一个新的巨型数组(尺寸小一倍),然后 mapToInt
制作另一个。这不是的工作原理。
Stream 是一个管道。在运行终端命令(sum
是终端)之前,不会发生任何事情。你可以检查一下:
Arrays.stream(queries).mapToInt(i -> {
System.out.println(i);
return i.intValue();
});
这不会打印任何内容。完全没有。因为这只是一个半生不熟的流过程,没有终端,所以它不会“流动”。如果您在上面调用 sum,那么打印就会开始发生。具体来说,终端(此处为
sum()
)开始“从流中提取值”。这样就往上走。 sum 向
mapToInt
请求一个值,为此,
mapToInt
向
limit
请求一个值(然后获取该值,将其滚动通过
i -> i
lambda,并将其提供给
sum
)。然后
limit
会向
Arrays.stream
询问一个值,然后实际从数组中读取单个项目。涉及ARE 中间跟踪器对象,但它们的大小不依赖于 N。例如,
Arrays.stream(queries)
返回的对象保存对
queries
数组的引用(大约 64 位数据,无论该数组有多大)是;只是一个指针),以及一个知道我们在哪里的 int 值1。 代表其中
limit
部分的对象只有一个
int
,用于跟踪到目前为止提供了多少个值。
limit
当它所拉出的东西用完时,就像没有更多的值可以提供或
limit
项目已经提供,以较早发生者为准。等等。因此,这些跟踪器对象到底有多大是一个实现细节,但是它们是“小”(至少相对于一百万个整数数组而言!),并且不依赖于流的大小。事实上,无限流可以存在,没有问题。他们确实这样做了 - 检查
Stream
本身的 API,例如,您可以在其中轻松创建一个返回无限量
1
值的流。[1] 我过于简单化了。流还具有根据某些情况可以并行化的特性。当涉及计数器时,并行化变得非常困难,因此这些跟踪器有点复杂。如果您想要完整的详细信息,请查看
Spliterator 和 StreamUtils
。但是,这种过于简单化的解释足以理解,任何流中间操作都不会让您面临内存不足的风险。