【问题标题】:How do streams internally process data?流如何在内部处理数据?
【发布时间】:2019-11-12 07:10:35
【问题描述】:

如果我在流上执行多个操作,它们是如何在内部计算的。

  1. 它的计算顺序是否与输入集合的顺序相同?
  2. 它是否将函数应用于所有元素,然后移动以评估下一个函数?或者它将所有功能应用于一个元素,然后是第二个元素......等等。
  3. 就性能而言,它与对集合执行的正常迭代有何不同。

    List<Integer> ee = new ArrayList<Integer>();
    Function<? super Integer, ? extends Integer> f1 = x -> x * 2;
    Function<? super Integer, ? extends Integer> f2 = x -> x * x;
    Function<? super Integer, ? extends Integer> f3 = x -> x / 2;
    ee.stream().map(f1.compose(f2.andThen(f3))).collect(Collectors.toList());

    ee.stream().map(f1).map(f2).map(f3).collect(Collectors.toList());

编辑问题以添加多个地图操作。在多个地图功能应用的情况下如何计算地图功能。在谈论非并行流时,元素的计算顺序是否仅取决于输入集合的类型(即,对于 list、linkedHashmap、sortedset 和 unordered 等是有序的)。另外,我能否对流的内部工作有更多的了解,以便更好地决定何时不建议使用流以及何时建议最多使用它们。(集合大小、序列性质等)

【问题讨论】:

  • 2.流只看到一个函数。它无法分解或部分应用它。
  • 您的标题显示“2 个或多个操作”,但实际上您只是将一个操作传递给流。如果您想要更多,请多次致电map。例如。 map(f1).map(f2).map(f3)/

标签: java java-8 java-stream


【解决方案1】:

订单

  1. 它的计算顺序是否与输入集合的顺序相同?

没有必要。有很多事情可以决定顺序。在流本身上,有unordered()parallel() 会影响它。然后,你也可以通过终端操作来影响它,例如forEach vs forEachOrdered()。对于collect,它取决于您收集到的底层Collection。对于toList(),它会按照您的流当前服务的顺序收集它们。因此,如果您没有致电unordered() 或其他任何东西,它将被订购。

来自official documentation

返回一个收集器,它将所有输入元素收集到一个列表中,以遇到顺序


动作管道

  1. 它是否将函数应用于所有元素,然后移动以评估下一个函数?或者它将所有功能应用于一个元素,然后是第二个元素......以此类推。

只要不应用终端操作,例如collect,什么都不会发生。它会记住您的动作并创建一个动作管道。一旦你开始,通过拥有collect,它将一个接一个地迭代所有项目并将完整的动作管道应用于它。因此,它将获取一个元素并将所有函数应用到它,然后将其填充到结果列表中,然后移动到下一个元素。

但是,在您的具体情况下,您只有一个 map(...) 操作。因此,您没有告诉流应用 3 个函数,而是使用函数中的方法将 3 个函数组合成一个大函数,并将该单个函数提供给流。因此,流只有一个功能可以使用,当应用时,它会一个接一个地执行所有 3 个操作。通常人们会使用多个map(...) 调用:

ee.stream()
    .map(f1)
    .map(f2)
    .map(f3)
    .collect(Collectors.toList());

性能

  1. 就性能而言,它与对集合执行的正常迭代有何不同。

一般来说,流附加了更多的逻辑。你需要构造一些对象,创建一些动作管道等。所以它引入了一些开销,但没什么大不了的。因此,只要您没有微小的迷你数据集并且您需要每一纳秒,就认为它与普通迭代一样好。

虽然有几个优点。一个可以是可读性(当然不总是)。另一个突出的优势是 Stream API 免费提供多线程。您只需致电parallel() 建议用法,如果Java 认为这是一个好主意,它将开始同时处理多个元素。

请记住,尽管多线程会带来大量开销。所以如果你的数据源不大,总体上可能会比较慢。

【讨论】:

  • 虽然上面的解释比较准确,但是我还是有几点不太清楚。用更多点编辑了问题..请看一下..我正在寻找特定于非并行流的答案。
  • 我所说的都适用于非并行流。所以它仍然成立。请注意,您不应以使现有答案无效的方式编辑您的问题。更愿意提出一个新问题。
【解决方案2】:
  1. 它的计算顺序是否与输入集合的顺序相同?

是的,因为 ArrayList 返回的 Spliterator 是 ordered

ee.stream().spliterator().hasCharacteristics(Spliterator.ORDERED); // prints true
  1. 它是否将函数应用于所有元素,然后移动以评估下一个函数?或者它将所有功能应用于一个元素,然后是第二个元素......等等。

第二个。 Stream 对映射函数的内部结构一无所知,因此将其视为单个(不可分割的)函数。

  1. 就性能而言,它与对集合执行的正常迭代有何不同。

这在 StackOverflow 上被问了很多次。一般来说,Streams 比它们的循环对应物要慢一些。

【讨论】:

  • @Zabuza 当然,它已经定义好了。如果 Stream 是有序的,则返回的 List 具有相同的顺序。
  • @Zabuza Javadoc 明确指出:“返回一个收集器,它将所有输入元素收集到一个列表中,按照遇到的顺序”。
  • 同意。在那种情况下,我认为您当前的解释可能具有误导性。由于可以随时交换实现。您刚刚共享的规范是关键部分。就像一个注释。
  • “它是计算出来的……”这个问题是模棱两可的,因为不清楚它是指处理过程还是结果。保证结果是有序的,但有意未指定处理。为了表明源是有序的,最好首先使用ee.spliterator() 而不是ee.stream().spliterator()
  • @Holger 我很难想象一个集合,其中非并行处理可能是无序的,但最终结果是有序的。我同意这是一种未指定的行为,但在实际应用中,我认为我们可以放心地依赖这一事实。
【解决方案3】:

关于第二点

(2. 它是否将函数应用于所有元素,然后移动到 评估下一个功能?或者它将所有功能应用于一个 元素,然后是第二个元素......等等。)

当终端操作启动时,流实现选择一个执行计划。中间操作分为无状态(filter()、map()、flatMap())和有状态(sorted()、limit()、distinct())操作。无状态操作是可以在不知道任何其他元素的情况下对元素执行的操作。例如,过滤操作只需要检查当前元素以确定是否包含或消除它,但排序操作必须先查看所有元素,然后才能知道首先发出哪个元素。

参考 - https://developer.ibm.com/languages/java/articles/j-java-streams-3-brian-goetz/

【讨论】:

    猜你喜欢
    • 2021-06-02
    • 1970-01-01
    • 2011-04-24
    • 2012-03-20
    • 1970-01-01
    • 2021-01-28
    • 2019-02-21
    • 1970-01-01
    • 2014-07-14
    相关资源
    最近更新 更多