【问题标题】:Length of an infinite IntStream?无限 IntStream 的长度?
【发布时间】:2018-07-18 08:27:29
【问题描述】:

我通过这个创建了一个 randomIntStream:

 final static PrimitiveIterator.OfInt startValue = new Random().ints(0, 60).iterator();

文档说这个流实际上是无穷无尽的。

我想了解在后台发生了什么。

ints(0,60) 正在生成无限的整数流。如果这是无限的,为什么我的机器没有泄漏任何内存?

我想知道,实际上真正生成了多少数字,这种实现是否会在流仍然结束的地方导致错误?或者这个流是否会不断地充满新的整数,因此它真的永远不会结束?

如果我已经问过这个问题,那么现在生成随机数的最佳实践是什么?

【问题讨论】:

  • 数字(流中的元素)在需要时生成,然后立即再次被遗忘(除非收集在列表中,这可能是个坏主意)。
  • 挑战与无限之美。您无法计算...根据您使用/存储这些数字的方式,您可以设法耗尽内存。但我想从技术上讲,您无法数出无限的数字,因为您必须等待无穷大……您必须在某个地方停下来
  • 你不需要每次都创建一个新的Random,你可以使用ThreadLocalRandom.current()为例。
  • 这里使用“内存泄漏”是不正确的。如果在程序员不打算分配内存时分配内存,这只是内存泄漏。你应该说“为什么不消耗无限量的内存”......但是......当然不可能消耗无限量的内存。
  • @ernest_k 实际上,您可以计算一些(但不是全部)无穷大。这个概念被称为可数无限。

标签: java random java-stream


【解决方案1】:

Streams do not (in general1) store all of their elements in any kind of a data structure:

没有存储空间。流不是存储元素的数据结构;相反,它通过计算操作的管道从数据结构、数组、生成器函数或 I/O 通道等源传送元素。

相反,每次流前进时,每个流元素都会一个接一个地计算。在您的示例中,当您调用 startValue.nextInt() 时,实际上会计算每个随机的 int

所以当我们做例如new Random().ints(0,60),流实际上是无限的这一事实不是问题,因为在我们执行一些遍历流的操作之前,实际上不会计算随机的 ints。一旦我们遍历了流,ints 只有在我们请求它们时才会被计算。

这是一个使用Stream.generate(也是无限流)的小例子,它显示了这种操作顺序:

Stream.generate(() -> {
    System.out.println("generating...");
    return "hello!";
})
.limit(3)
.forEach(elem -> {
    System.out.println(elem);
});

该代码的输出是:

generating...
hello!
generating...
hello!
generating...
hello!

请注意,我们的生成器供应商在每次调用 forEach 消费者之前被调用一次,仅此而已。如果我们不使用limit(3),程序可以永远运行,但不会耗尽内存。

如果我们使用new Random().ints(0,60).forEach(...),它会以同样的方式工作。在每次调用forEach 消费者之前,该流将执行一次random.nextInt(60)。除非我们使用一些需要它的操作,例如 distinct() 或收集器而不是 forEach,否则这些元素不会在任何地方累积。


  1. 某些流可能在后台使用数据结构进行临时存储。例如,在树遍历算法中使用堆栈是很常见的。此外,某些流(例如使用 Stream.Builder 创建的流)将需要数据结构来放入其元素。

【讨论】:

    【解决方案2】:

    流是无限的¹,因此您可以根据需要生成任意数量的整数,而不会用完。这并不意味着它会在您不要求任何内容时继续生成它们。

    实际生成多少数字取决于您编写的代码。每次从迭代器中检索值时,都会生成一个值。没有在后台生成,因此没有使用“额外”内存。

    ¹就您的一生而言,请参阅 Eran 的回答

    【讨论】:

      【解决方案3】:

      准确地说,

      IntStream java.util.Random.ints(int randomNumberOrigin, int randomNumberBound) 返回:

      实际上无限的伪随机 int 值流,每个都符合给定的原点(包括)和绑定(不包括)。

      这并不意味着无限。查看 Javadoc,您会看到一个实现说明,说明它实际上将返回的 IntStream 限制为 Long.MAX_VALUE 元素:

      实施说明:

      这个方法实现相当于ints(Long.MAX_VALUE, randomNumberOrigin, randomNumberBound)

      当然Long.MAX_VALUE是一个很大的数字,因此返回的IntStream可以看作是“有效”的,没有限制。例如,如果您每秒消耗该流的 1000000 个ints,那么您将需要大约 292471 年的时间才能用完元素。

      也就是说,正如其他答案所述,IntStream 仅生成其消费者所需的数量(即消耗 ints 的终端操作)。

      【讨论】:

      • 作为额外的考虑,当源是java.util.Random,即不是具有不同算法的子类时,流将在 2⁶³ 元素后“突然”停止,但它也会在 2⁴⁸ 元素后重复,因此,如果您在 2⁴⁸ 元素之后没有完成任务,那么您的问题也不太可能用超过 2⁶³ 元素来解决……
      【解决方案4】:

      正如@Kayaman 在他的回答中所说。流是无限的,因为数字可以永远生成。重点在于can这个词。只有在您真正请求时,它才会生成数字。它不仅会生成 X 数量的数字,然后将它们存储在某个地方(除非你告诉它这样做)。

      所以如果你想生成 n(其中 n 是一个整数)随机数。你可以在Random#ints()返回的流上调用ints(0, 60)ints(n, 0, 60)的重载:

      new Random().ints(n, 0, 60)
      

      上面仍然不会生成 n 个随机数,因为它是一个 IntStream,它是延迟执行的。因此,当不使用终端操作(例如collect()forEach())时,什么都不会发生。

      【讨论】:

        【解决方案5】:

        创建生成器不会生成任何数字。从概念上讲,这个生成器将不断生成新的数字;当被询问时,它不会返回下一个值。

        【讨论】:

          猜你喜欢
          • 2012-01-25
          • 1970-01-01
          • 1970-01-01
          • 2014-08-26
          • 2018-05-11
          • 1970-01-01
          • 1970-01-01
          • 2016-02-14
          • 2020-08-23
          相关资源
          最近更新 更多