当你处理更大的数据或无限的流时,懒惰laziness是一个真正的福音, 处理数据时,我们不确定何时使用已处理的数据。eager急切的立即处理会以牺牲性能为代价,客户端可能只是使用一小部分数据。或者,根据某些条件,客户端甚至可能不需要利用该数据。
延迟处理应该基于“ 按需处理 ”策略。
目前的趋势是大数据的并行和实时处理,数据量不断增长且高性能两种要求带来挑战,Java Collections API模型可满足未来的这种跳转,Java 8 Streams API完全基于“ 仅按流程和按需处理 ”策略,因此必然支持懒惰laziness。
在Java 8 Streams API中,管道中间的操作都是惰性的,并且其内部处理模型已经过优化,使其能够使用大量数据和高性能进行处理。让我们看一下它的例子:
<font><i>//Created a Stream of a Students List</i></font><font> </font><font><i>//attached a map operation on it</i></font><font> Stream < String > streamOfNames = students.stream() .map(student - > { System.out.println(</font><font>"In Map - "</font><font> + student.getName()); <b>return</b> student.getName(); }); </font><font><i>//Just to add some delay</i></font><font> <b>for</b> (<b>int</b> i = 1; i <= 5; i++) { Thread.sleep(1000); System.out.println(i + </font><font>" sec"</font><font>); } </font><font><i>//Called a terminal operation on the stream</i></font><font> streamOfNames.collect(Collectors.toList()); </font>
输出:
1 sec 2 sec 3 sec 4 sec 5 sec In Map - Tom In Map - Chris In Map - Dave
这里有一个在流上调用的Map操作,然后我们将延迟5秒,然后调用收集操作(终端操作)。为了证明懒惰,我们延迟了5秒。
性能优化:
如上所述,设计流的内部处理模型以优化处理流程。在处理流中,我们通常最终创建管道的各种中间操作和终端操作。各种中间操作通常会可能合并在一次性通过处理。
List < String > ids = students.stream() .filter(s - > { System.out.println(<font>"filter - "</font><font> + s); <b>return</b> s.getAge() > 20; }) .map(s - > { System.out.println(</font><font>"map - "</font><font> + s); <b>return</b> s.getName(); }) .limit(3) .collect(Collectors.toList()); </font>
输出:
filter - 8 map - 8 filter - 9 map - 9 filter - 10 filter - 11 map - 11
上面的例子演示了这种行为,我们有两个中间操作,即map和filter。输出显示,它们都不会在可用流的整个大小上独立执行。
首先,id-8通过filter并立即移动到map。id-9的情况也是如此,而id-10没有通过filter检查。我们可以看到id-8,一旦通过过filter就立即可用于map操作,无论在filter之前还有多少元素仍然在流中排列。
短路方法:
Java 8 Streams API借助于短路操作优化了流处理。短路方法一旦满足条件就结束流处理。在通常的短路操作中,一旦满足条件,就会中断所有处于管道之前的中间操作。一些中间操作和终端操作具有此行为。
要查看它的工作原理,请尝试以下示例字符串名称列表:
<font><i>//Somewhere down the line</i></font><font> </font><font><i>//Just want two names from the steram</i></font><font> namesStream.limit(2).collect(Collectors.toList()); </font>
操作代码见下面:第一个流操作是(实际上没有意义)map,它以大写形式返回名称。第二个操作是filter,它只返回以“B”开头的名称。现在某个地方,如果我们通常调用它上面的Collect操作,map和filter是否看到处理列表中的所有名称?
<font><i>//List of names</i></font><font> List < String > names = Arrays.asList(<b>new</b> String[] { </font><font>"barry"</font><font>, </font><font>"andy"</font><font>, </font><font>"ben"</font><font>, </font><font>"chris"</font><font>, </font><font>"bill"</font><font> }); </font><font><i>//map and filter are piped and the stream is stored</i></font><font> Stream < String > namesStream = names.stream() .map(n - > { System.out.println(</font><font>"In map - "</font><font> + n); <b>return</b> n.toUpperCase(); }) .filter(upperName - > { System.out.println(</font><font>"In filter - "</font><font> + upperName); <b>return</b> upperName.startsWith(</font><font>"B"</font><font>); }); </font>
但是如果我们在Collect之前设置limit操作,则输出会发生显着变化。
输出:
In map - barry In filter - BARRY In map - andy In filter - ANDY In map - ben In filter - BEN
我们可以清楚地看到limit(虽然它最近从其他地方调用,它是管道中的最后一个中间操作)对map和过滤操作有影响。整个管道说,我们想要前两个以字母“B”开头的名字。一旦管道处理以“B”开头的前两个名称,map和过滤器甚至不处理其余的名称。
现在,这可以证明是一个非常巨大的性能提升。考虑一下,如果我们的列表包含几千个名称,并且我们只想要匹配某个过滤条件的前几个名称,那么一旦我们得到预期的元素,就会跳过其余元素的处理。
anyMatch,allMatch,noneMatch,findFirst,findAny,limit和sub-stream等操作是Steams API中的这种短路方法。