Skip to content

Commit db12128

Browse files
committed
修正排版
1 parent 4a8bfa3 commit db12128

File tree

1 file changed

+6
-8
lines changed

1 file changed

+6
-8
lines changed

6-Stream Pipelines.md

+6-8
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,16 @@ Stream上的所有操作分为两类:中间操作和结束操作,中间操
3535

3636
## 一种直白的实现方式
3737

38-
仍然考虑上述求最长字符串的程序,一种直白的方式是为每一次函数调用都执一次迭代,并将处理中间结果放到某种数据结构中(比如数组,容器等)。具体说来,就是调用*filter()*方法后立即执行,选出所有以*A*开头的字符串并放到一个列表list1中,之后让list1传递给*mapToInt()*方法并立即执行,生成的结果放到list2中,最后遍历list2找出最大的数字作为最终结果。程序的执行流程如下如所示:
38+
<img src="./Figures/Stream_pipeline_naive.png" width="500px" align="right" alt="Stream_pipeline_naive"/>
3939

40-
<img src="./Figures/Stream_pipeline_naive.png" width="500px" align="left" alt="Stream_pipeline_naive"/>
41-
42-
<br>
40+
仍然考虑上述求最长字符串的程序,一种直白的方式是为每一次函数调用都执一次迭代,并将处理中间结果放到某种数据结构中(比如数组,容器等)。具体说来,就是调用*filter()*方法后立即执行,选出所有以*A*开头的字符串并放到一个列表list1中,之后让list1传递给*mapToInt()*方法并立即执行,生成的结果放到list2中,最后遍历list2找出最大的数字作为最终结果。程序的执行流程如如所示:
4341

4442
这样做实现起来非常简单直观,但有两个明显的弊端:
4543

4644
1. 迭代次数多。迭代次数跟函数调用的次数相等。
4745
2. 频繁产生中间结果。每次函数调用都产生一次中间结果,存储开销无法接受。
4846

49-
这种弊端使得效率地下,根本无法接受。如果不使用Stream API我们都知道上述代码该如何在一次迭代中完成,大致是如下形式:
47+
这种弊端使得效率底下,根本无法接受。如果不使用Stream API我们都知道上述代码该如何在一次迭代中完成,大致是如下形式:
5048

5149
```Java
5250
int longest = 0;
@@ -72,15 +70,15 @@ for(String str : strings){
7270

7371
注意这里使用的是*“操作(operation)”*一词,指的是“Stream中间操作”的操作,很多Stream操作会需要一个回调函数(Lambda表达式),因此一个完整的操作是*<数据来源,操作,回调函数>*构成的三元组。Stream中使用Stage的概念来描述一个完整的操作,并用某种实例化后的*PipelineHelper*来代表Stage,将具有先后顺序的各个Stage连到一起,就构成了整个流水线。跟Stream相关类和接口的继承关系如下:
7472

75-
<img src="./Figures/Java_stream_pipeline_classes.png" width="400px" align="left" alt="Java_stream_pipeline_classes"/>
73+
<img src="./Figures/Java_stream_pipeline_classes.png" width="500px" align="right" alt="Java_stream_pipeline_classes"/>
7674

7775
图中还有*IntPipeline*, *LongPipeline*, *DoublePipeline*没有画出,这三个类专门为三种基本类型(不是包装类型)而定制的,跟*ReferencePipeline*是并列关系。图中*Head*用于表示第一个Stage,即调用调用诸如*Collection.stream()*方法产生的Stage,很显然这个Stage里不包含任何操作;*StatelessOp**StatefulOp*分别表示有状态和无状态的Stage,对应与有状态和无状态的中间操作。
7876

7977
<br>
8078

8179
一个可能的流水线示意图如下:
8280

83-
<img src="./Figures/Stream_pipeline_example.png" width="500px" align="left" alt="Stream_pipeline_example"/>
81+
<img src="./Figures/Stream_pipeline_example.png" width="600px" align="left" alt="Stream_pipeline_example"/>
8482

8583
<br>
8684

@@ -96,7 +94,7 @@ for(String str : strings){
9694

9795
有了上面的协议,相邻Stage之间调用就很方便了,每个Stage都会将自己的操作封装到一个Sink里,前一个Stage只需调用后一个Stage的*accept()*方法即可,并不需要知道其内部是如何处理的。当然对于有状态的操作,Sink的*begin()**end()*方法也是必须实现的。比如Stream.sorted()是一个有状态的中间操作,其对应的*Sink.begin()*方法可能创建一个乘放结果的容器,而*accept()*方法负责将元素添加到该容器,最后*end()*负责对容器进行排序。对于短路操作,*Sink.cancellationRequested()*也是必须实现的,比如*Stream.findFirst()*是短路操作,只要找到一个元素,*cancellationRequested()*就应该返回*true*,以便调用者尽快结束查找。Sink的四个接口方法常常相互协作,共同完成计算任务。
9896

99-
有了Sink对操作的包装,Stage之间的调用问题就解决了,执行时只需要从流水线的head开始依次调用每个Stage对应的*{Sink.begin(), accept(), cancellationRequested(), end()}*方法就可以了。一种可能的*Sink.accept()*方法流程是这样的:
97+
有了Sink对操作的包装,Stage之间的调用问题就解决了,执行时只需要从流水线的head开始依次调用每个Stage对应的{Sink.begin(), accept(), cancellationRequested(), end()}方法就可以了。一种可能的*Sink.accept()*方法流程是这样的:
10098

10199
```Java
102100
void accept(U u){

0 commit comments

Comments
 (0)