从Infinite Stream派生的排序流无法迭代

时间:2018-05-01 21:49:49

标签: java java-stream

import java.util.stream.*;
import java.util.*;

class TestInfiniteStream {
    public static void main(String args[]) {
        IntStream infiniteStream = new Random().ints();
        IntStream sortedStream = infiniteStream.sorted();

        sortedStream.forEach(i -> System.out.println(i));
    }
}

编译并执行此代码后,我收到以下错误。

Exception in thread "main" java.lang.IllegalArgumentException: Stream size exceeds max array size

对流进行排序是否会在无限流中失败?

3 个答案:

答案 0 :(得分:4)

在无限流中对流进行排序会失败吗?”的简单答案是“是。sorted()是一个有状态的中间操作,它具有在将任何元素传递给下游操作之前,通过缓冲整个内容并对其进行排序来实现。

理论上,它不需要那样。由于您使用的forEach已明确指定为以未定义的顺序处理元素,因此在new Random().ints().sorted().forEach(System.out::println);用例中可以省略排序步骤。但即使你使用forEachOrdered,也有理论上可以实现的正确答案。由于您的流是无限的并且将重复包含所有int值,因此正确的排序输出将永远打印-2147483648==Integer.MIN_VALUE),因为这是该流中无限次包含的最小值

但是,为了给出正确的答案,实现将需要特定的代码来处理这种情况,这没有多大实际价值。相反,实现处理这种情况就像流场景的任何其他排序一样,对于无限流将失败。

在这种特定情况下,流具有导致不同的异常异常消息的优化。作为Eugene pointed out,此流的行为类似于Long.MAX_VALUE==2⁶³)元素的固定大小流,而不​​是真正无限的流。这是公平的,考虑到Random生成的流将在2⁴⁸值之后重复,因此整个流在结束之前重复32768次而不是永远运行。无论如何,在处理9223372036854775807元素之后,你不太可能目睹这种“突然”的结局。但是这种优化的结果是流将以“流大小超过最大数组大小”消息快速失败,而不是在经过一些处理后失败并出现“OutOfMemoryError”。

如果您删除尺寸信息,例如通过

new Random().ints().filter(x -> true).sorted().forEach(System.out::println);

操作将尝试缓冲,直到java.lang.OutOfMemoryError失败。

也是如此
IntStream.generate(new Random()::nextInt).sorted().forEach(System.out::println);

首先不向流提供大小信息。在任何一种情况下,它都不会在排序开始之前对缓冲进行排序。

如果你想在评论中说出“元素限制的分类运行”,你必须在排序前应用限制,例如

new Random().ints().limit(100).sorted().forEach(System.out::println);

尽管仍然使用大小的流更有效,例如

new Random().ints(100).sorted().forEach(System.out::println);

答案 1 :(得分:2)

不,你无法对无限流进行排序。

您的无限流new Random().ints()产生的整数多于存储在数组(或任何数组)中的整数,后者在后台用于存储要排序的整数。数组当然不能保持无限数量的整数; only a capacity close to Integer.MAX_VALUE numbers

退后一步,任何人或任何东西如何排序无数个整数?什么都没有,也没有人可以;至少需要无限的时间。

  

对流进行排序是否会在无限流中失败?

你有点回答了自己的问题; IllegalArgumentException是失败的具体原因。它只是因为您创建了一个试图执行此操作的程序,并且遇到了Java数组限制。

sorted方法将在排序任何内容之前尝试读取整个流。它在读取整个流之前不会进行任何中间排序,因此不会进行任何排序,部分或全部。

答案 2 :(得分:0)

这是一个有趣的问题,不幸的是你的关键描述在评论中。首先,它并不是一个真正的无限的"在您的情况下,负责的分裂者RandomIntsSpliterator甚至会说:

  

......还通过对待"无限"等同于Long.MAX_VALUE ...

现在有趣的部分,分裂者将报告这些特征:

public int characteristics() {
        return (Spliterator.SIZED | Spliterator.SUBSIZED |
                Spliterator.NONNULL | Spliterator.IMMUTABLE);
}

好吧,我不知道为什么的原因,但报告SIZEDSUBSIZED无限流...可能无关紧要要么(因为你通常用limit链接这些)。

因为报告了SIZED(大小为Long.MAX_VALUE),因此内部SizedIntSortingSink有一个带有支票的接收器:

if (size >= Nodes.MAX_ARRAY_SIZE)
      throw new IllegalArgumentException(Nodes.BAD_SIZE);

显然会失败。

对比IntStream.generate 没有报告SIZED - 这对我来说很有意义,所以整个输入必须由sorted缓冲,然后再处理到终端操作;很明显,这会因OutOfMemory而失败。

证明distinct不会在这里充当障碍,等待处理所有值,这也很有趣。相反,一旦它知道以前没有看到它,它就可以将它们传递给终端操作:

Random r = new Random();
IntStream.generate(() -> r.nextInt())
        .distinct()
        .forEach(System.out::println);

在最终死于OutOfMemory之前,这将持续相当长的一段时间。或者正如Holger在评论中非常好地补充的那样,它也可能会挂起:

 IntStream.generate(() -> new Random()
          .nextInt(2))
          .distinct()
          .forEach(System.out::println);