Java 8 Stream:limit()和skip()之间的区别
谈论Stream
,当我执行这段代码的时候
public class Main { public static void main(String[] args) { Stream.of(1,2,3,4,5,6,7,8,9) .peek(x->System.out.print("\nA"+x)) .limit(3) .peek(x->System.out.print("B"+x)) .forEach(x->System.out.print("C"+x)); } }
我得到这个输出
A1B1C1 A2B2C2 A3B3C3
因为限制我的stream到前三个组件迫使行动A , B和C只能执行三次。
试图通过使用skip()
方法对最后三个元素执行类似的计算,显示了不同的行为:this
public class Main { public static void main(String[] args) { Stream.of(1,2,3,4,5,6,7,8,9) .peek(x->System.out.print("\nA"+x)) .skip(6) .peek(x->System.out.print("B"+x)) .forEach(x->System.out.print("C"+x)); } }
输出这个
A1 A2 A3 A4 A5 A6 A7B7C7 A8B8C8 A9B9C9
为什么在这种情况下,正在执行A1到A6的动作? 它必须与限制是短路状态中间操作的事实有关,而跳过不是,但我不明白这个属性的实际含义。 只是“ 跳过之前的每个动作被执行,而不是每个人都在限制之前”?
你在这里有两个streampipe道。
这些streampipe道每个都由一个源,几个中间操作和一个terminal操作组成。
但是中间操作是懒惰的。 这意味着什么都不会发生,除非下游操作需要一个项目。 当它这样做时,中间操作完成所需的所有工作,然后再等待,直到另一个项目被请求,等等。
terminal操作通常是“热切”的。 也就是说,他们要求stream中所有需要完成的项目。
所以你应该真的把stream水线想象成forEach
将后面的数据stream请求下一个数据stream,然后这个数据stream请求stream中的数据stream,以此类推,直到源数据。
考虑到这一点,让我们看看你的第一个pipe道有什么:
Stream.of(1,2,3,4,5,6,7,8,9) .peek(x->System.out.print("\nA"+x)) .limit(3) .peek(x->System.out.print("B"+x)) .forEach(x->System.out.print("C"+x));
所以, forEach
是要求第一个项目。 这意味着“B” peek
需要一个项目,并要求limit
输出stream,这意味着limit
将需要问“ peek
,这源于。 一个项目被给出,并一直到forEach
,你得到你的第一行:
A1B1C1
forEach
要求另一个项目,然后另一个项目。 并且每次,请求被传播到stream中,并被执行。 但是当forEach
要求第四项时,当请求达到limit
,它知道它已经给出了所有允许的项目。
因此,它不是要求“A”偷看另一个项目。 它立即表明它的物品已经耗尽,因此不再执行任何动作,并终止forEach
动作。
第二条pipe线发生了什么?
Stream.of(1,2,3,4,5,6,7,8,9) .peek(x->System.out.print("\nA"+x)) .skip(6) .peek(x->System.out.print("B"+x)) .forEach(x->System.out.print("C"+x));
再次, forEach
要求第一个项目。 这是传播回来。 但是,当它skip
下游时,它知道它必须从其上游请求6个物品,然后才能通过下游。 所以它在“A” peek
上游提出请求,在不传递下游的情况下使用它,提出另一个请求,等等。 因此,“A”偷看获得一个项目的6个请求,并产生6打印,但这些项目不传递。
A1 A2 A3 A4 A5 A6
在skip
的第七个请求中,该项目被传递给“B”,并从它到forEach
,所以完整的打印完成:
A7B7C7
那就像以前一样 skip
现在,每当它收到一个请求,向上游请求一个项目并将其传递给下游,因为它“知道”它已经完成了跳过的工作。 所以其余的印刷品都要经过整个pipe道,直到源头耗尽。
stream式pipe道的stream畅符号是造成这种混淆的原因。 这样想一想:
limit(3)
所有的stream水线操作都是懒惰的评估,除了terminal操作的触发器forEach()
,它触发“pipe道的执行” 。
当stream水线执行时,中介stream定义将不会对“之前”或“之后”发生的情况作出任何假设。 他们所做的只是将inputstream转换为输出stream:
Stream<Integer> s1 = Stream.of(1,2,3,4,5,6,7,8,9); Stream<Integer> s2 = s1.peek(x->System.out.print("\nA"+x)); Stream<Integer> s3 = s2.limit(3); Stream<Integer> s4 = s3.peek(x->System.out.print("B"+x)); s4.forEach(x->System.out.print("C"+x));
-
s1
包含9个不同的Integer
数值。 -
s2
窥视所有通过它并打印出来的值。 -
s3
将前三个值传递给s4
并在第三个值后中止pipe道。s3
没有产生更多的值。 这并不意味着没有更多的价值在pipe道中。s2
仍然会产生(和打印)更多的值,但没有人请求这些值,从而停止执行。 -
s4
再次偷看所有通过它的值并打印出来。 -
forEach
消费和打印任何s4
传递给它。
想想这样。 整个stream是完全懒惰的。 只有terminal操作主动从pipe道中拉出新的值。 从s4 <- s3 <- s2 <- s1
拉出3个数值后, s3
将不再产生新的数值,并且不再从s2 <- s1
取任何数值。 虽然s1 -> s2
仍然能够产生4-9
,但这些值只是从来没有从pipe道中拉出,因此从不打印s2
。
skip(6)
用skip()
会发生同样的事情:
Stream<Integer> s1 = Stream.of(1,2,3,4,5,6,7,8,9); Stream<Integer> s2 = s1.peek(x->System.out.print("\nA"+x)); Stream<Integer> s3 = s2.skip(6); Stream<Integer> s4 = s3.peek(x->System.out.print("B"+x)); s4.forEach(x->System.out.print("C"+x));
-
s1
包含9个不同的Integer
数值。 -
s2
窥视所有通过它并打印出来的值。 -
s3
消耗前6个值, “跳过它们” ,这意味着前6个值不会传递给s4
,只有后续值。 -
s4
再次偷看所有通过它的值并打印出来。 -
forEach
消费和打印任何s4
传递给它。
这里重要的是s2
没有意识到剩余的pipe道跳过任何值。 s2
窥视所有的价值,而不pipe事后会发生什么。
另一个例子:
考虑这个pipe道, 这是在这篇博客文章中列出
IntStream.iterate(0, i -> ( i + 1 ) % 2) .distinct() .limit(10) .forEach(System.out::println);
当你执行上述操作时,程序永远不会停止。 为什么? 因为:
IntStream i1 = IntStream.iterate(0, i -> ( i + 1 ) % 2); IntStream i2 = i1.distinct(); IntStream i3 = i2.limit(10); i3.forEach(System.out::println);
意思是:
-
i1
生成无限量的交替值:i1
,… -
i2
消耗之前遇到的所有值,仅传递“新”值,即总共有2个值来自i2
。 -
i3
传递10个值,然后停止。
这个algorithm将永远不会停止,因为i3
在0
和1
之后等待i2
产生8个更多的值,但是这些值从不出现,而i1
从不停止向i2
馈送值。
在pipe道的某一点上,已经产生了10多个值并不重要。 重要的是i3
从来没有见过这10个值。
回答你的问题:
只是“跳过之前的每个动作被执行,而不是每个人都在限制之前”?
不。 执行skip()
或limit()
之前的所有操作。 在你的执行中,你会得到A1
– A3
。 但是limit()
可能会使pipe道短路,一旦发生感兴趣事件(达到限制),就会中止消耗。
单独考察蒸汽运行是完全亵渎的,因为这不是如何评估蒸汽。
谈到极限(3) ,这是一个短路操作,因为考虑它,无论是在 limit
之前还是之后的操作都是有意义的,在stream中有一个极限将在获得n个元素之后停止迭代直到极限操作,但是这并不意味着只有n个stream元素会被处理。 以这个不同的stream操作为例
public class App { public static void main(String[] args) { Stream.of(1,2,3,4,5,6,7,8,9) .peek(x->System.out.print("\nA"+x)) .filter(x -> x%2==0) .limit(3) .peek(x->System.out.print("B"+x)) .forEach(x->System.out.print("C"+x)); } }
会输出
A1 A2B2C2 A3 A4B4C4 A5 A6B6C6
这似乎是正确的,因为限制正在等待3个stream元素通过操作链,尽pipe6个stream元素被处理。
所有stream都基于分割器,基本上有两个操作:advance(向前移动一个元素,类似于迭代器)和split(自我分割为任意位置,适合并行处理)。 您可以随时停止input元素(这是通过limit
来完成的),但不能跳到任意位置(在Spliterator
界面中没有这样的操作)。 因此, skip
操作需要实际读取来源的第一个元素,而忽略它们。 请注意,在某些情况下,您可以执行实际跳转:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9); list.stream().skip(3)... // will read 1,2,3, but ignore them list.subList(3, list.size()).stream()... // will actually jump over the first three elements