流式管道的流畅符号是导致这种混淆的原因。可以这样考虑:
limit(3)
除了 forEach()
是一个终止操作,会触发 "执行管道",所有的流水线操作都是惰性求值的。
当管道被执行时,中间流定义不会对"之前"或"之后"发生的事情作出任何假设。它们所做的只是将输入流转换为输出流:
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
并在第三个值后中止管道。 这并不意味着管道中没有更多的值。 s2
仍然会生成(和打印)更多的值,但是没有人请求这些值,因此执行停止了。
s4
再次查看通过它的所有值并将它们打印出来。
forEach
消耗并打印 s4
传递给它的任何内容。
可以这样想。整个流完全是惰性的。只有终端操作才会主动从管道中“拉”取新值。在从 s4 < - s3 < - s2 < - s1
中提取了3个值之后,s3
将不再产生新值,并且它不再从 s2 <- s1
中提取任何值。虽然 s1 -> s2
仍然能够生成 4-9
,但这些值从未从管道中提取,因此也没有被 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
不知道其余管道是否跳过了任何值。 s2
独立于之后发生的事情查看所有值。
另一个例子:
考虑这个流水线,该流水线在此博客文章中列出
IntStream.iterate(0, i -> ( i + 1 ) % 2)
.distinct()
.limit(10)
.forEach(System.out::println)
当你执行上述代码时,程序永远不会停止。为什么?因为:
IntStream i1 = IntStream.iterate(0, i -> ( i + 1 )
IntStream i2 = i1.distinct();
IntStream i3 = i2.limit(10);
i3.forEach(System.out::println);
这意味着:
- i1 生成一个无限数量的交替值:0、1、0、1、0、1……
- i2 消耗之前遇到的所有值,仅传递“新”的值,即总共有 2 个值从 i2 输出。
- i3 传递 10 个值,然后停止。
这个算法永远不会停止,因为 i3 等待 i2 在 0 和 1 之后产生更多的 8 个值,但这些值从未出现,而 i1 永远不会停止向 i2 提供值。
在管道中的某个时刻产生了超过 10 个值并不重要。重要的是 i3 从未看到那 10 个值。
回答你的问题:
“只是意思是在跳过之前执行每个操作,而在达到限制之前不执行每个操作吗?”
不是。在 skip() 或 limit() 之前的所有操作都会执行。在你的两个执行中,你得到 A1 - A3。但是,limit() 可能会中断管道,一旦发生感兴趣的事件(达到限制)就终止值的消耗。
skip()
是一个有状态的中间操作。但是,根据 Javadoc 的说法,短路操作是可能在有限时间内处理无限流的操作。这有点说得通,因为skip()
可能需要创建一个不同的无限流,而limit()
只需要取前几个。我不知道为什么这种差异(如果真的是这样)会影响您的输出。 - bcsb1001