【问题标题】:Java 8 Stream vs Collection StorageJava 8 流与集合存储
【发布时间】:2015-06-29 12:39:56
【问题描述】:

我一直在阅读 Java 8 Streams 以及从数据源流式传输数据的方式,而不是从整个集合中提取数据。

我在an article 上读到的这句话特别是关于 Java 8 中的流。

没有存储空间。流没有存储值;它们通过计算步骤的管道从源(可以是数据结构、生成函数、I/O 通道等)携带值。

我理解从源中逐个流式传输数据的概念。我不明白的是,如果您从集合中流式传输,怎么没有存储空间?该集合已经存在于堆上,您只是从该集合中流式传输数据,该集合已经存在于“存储”中。

如果我只是用标准的 for 循环遍历集合,那么在内存占用方面有什么区别?

【问题讨论】:

    标签: java memory collections java-8 java-stream


    【解决方案1】:
    1. 集合是一种数据结构。根据问题,您决定使用哪个集合,如 ArrayList、LinekedList(考虑时间和空间复杂度)。 Stream 只是一种处理工具,它让您的生活变得轻松。

    2. 另外的区别是,你可以将Collection视为内存数据结构,你可以在其中添加、删除元素。 在 Stream 中,您可以执行两种操作:

      一个。 中间操作:过滤、映射、排序、限制结果集
      湾。 终端操作:forEach,将结果集收集到一个集合中。

      但是,如果您注意到,您无法使用流添加或删除元素。

    3. Stream是一种iterator,你可以通过stream来遍历collection。注意,stream只能遍历一次,我举个例子让大家更好理解:

    示例 1:

    List<String> employeeNameList = Arrays.asList("John","Peter","Sachin");
        Stream<String> s = employeeNameList.stream();
    
        // iterate through list
        s.forEach(System.out :: println);  // this work's perfectly fine
        s.forEach(System.out :: println);  // you will get IllegalStateException, stating stream already operated upon
    

    因此,您可以推断出,您可以根据需要多次迭代集合。但是对于流,一旦你 iterate ,它就不会记住它应该做什么。所以,你需要再次指示它。

    我希望,很清楚。

    【讨论】:

      【解决方案2】:

      以前的答案大多是正确的。然而,仍然有一个更直观的响应(对于登陆这里的谷歌乘客):

      将流视为文本的 UNIX 管道: 猫输入.file | sed ... | grep ... > output.file

      与处理后的输入数据相比,通常这些 UNIX 文本实用程序将消耗少量 RAM。

      情况并非总是如此。想想“排序”。该算法需要将中间内容保存在内存中。 流也是如此。有时需要时态数据。大多数时候不会。

      作为一个额外的比喻,“云无服务器 API”在某种程度上遵循相同的 UNIX 管道或 Java 流设计。 在有一些输入数据要处理之前,它们不存在于内存中。云操作系统将启动它们并注入输入数据。输出会逐渐发送到其他地方,因此 cloud-serverless-API 不会消耗很多资源(大多数情况下)。

      在这种情况下不是绝对的“真实”。

      【讨论】:

        【解决方案3】:

        关于流和存储的陈述意味着流没有任何自己的存储。如果流的源是一个集合,那么显然该集合具有存储元素的存储空间。

        让我们从那篇文章中举一个例子:

        int sum = shapes.stream()
                        .filter(s -> s.getColor() == BLUE)
                        .mapToInt(s -> s.getWeight())
                        .sum();
        

        假设shapes 是具有数百万个元素的Collection。有人可能会想象filter 操作将遍历源中的元素并创建一个临时结果集合,其中可能还包含数百万个元素。然后mapToInt 操作可能会遍历该临时集合并生成其结果以求和。

        这不是它的工作原理。没有临时的、中间的收集。流操作是流水线操作的,因此来自filter 的元素会通过mapToInt 传递到sum,而不是存储到集合中或从集合中读取。

        如果流源不是集合(例如,从网络集合中读取元素),则根本不需要任何存储。如下管道:

        int sum = streamShapesFromNetwork()
                        .filter(s -> s.getColor() == BLUE)
                        .mapToInt(s -> s.getWeight())
                        .sum();
        

        可能会处理数百万个元素,但它不需要在任何地方存储数百万个元素。

        【讨论】:

        • 但是如果我创建了一个像 Stream&lt;String&gt; stream = Stream.of("test", "test", "test", "test"); 这样的流对象,我可以稍后访问这个对象。它不存储元素吗?
        • @Coffemanz 当然,但Stream.of 是一个特例。这是一个可变参数方法,因此传递给它的参数被收集到一个数组中,然后用作流的源。 (所有 varargs 方法都通过将参数收集到一个数组中来工作;这对流来说并不特殊。)现在考虑类似Stream&lt;String&gt; s2 = Stream.of("test", "test", "test", "test").map(String::toUpperCase); 的东西。任何地方都没有包含四个"TEST" 实例的集合或数组。大写的值不存储,而是延迟生成。
        【解决方案4】:

        流只是数据的一个视图,它没有自己的存储空间,您无法通过以下方式修改底层集合(假设它是构建在集合之上的流)流。这就像“只读”访问。

        如果您有任何 RDBMS 经验 - 这与“视图”的概念完全相同。

        【讨论】:

          【解决方案5】:

          将流视为连接到作为数据结构的水箱的喷嘴。喷嘴没有自己的存储空间。当然,流提供的水(数据)来自具有存储的来源,但流本身没有存储。将另一个喷嘴(流)连接到您的水箱(数据结构)不需要存储全新的数据副本。

          【讨论】:

          • 感谢您的回复!我了解数据流部分,但是如果我直接使用集合而不是流它,那么内存(存储)开销是多少?如果我直接使用集合,我会访问堆上的集合,如果我流式传输它,我仍然会从驻留在堆上的集合中流式传输数据。无论哪种方式,内存占用量在两种方法中都保持不变。如果我错了/在这里遗漏了什么,请纠正我。
          • @user3587411:本质上没有区别。一种方法可能会使用更多的堆栈帧和临时对象,但这只是几个字节。
          • “无论哪种方式,两种方法的内存占用都保持不变。”流的目的不是为了节省内存。
          • @Radiodef 我明白了,除了 lambda 支持之外,在常规循环上使用流进行集合是否有目的/优势,并确保上面提到的数据是只读的 (@alfasin)。
          猜你喜欢
          • 2014-05-04
          • 2017-08-08
          • 1970-01-01
          • 2015-10-20
          • 1970-01-01
          • 2015-08-03
          • 1970-01-01
          • 1970-01-01
          • 2014-11-11
          相关资源
          最近更新 更多