VSO*_*VSO 8 java lazy-loading java-stream
我最近已经阅读了很多关于Java 8流的文章,并且有几篇关于Java 8流加载延迟的文章:这里和这里.我似乎无法摆脱延迟加载完全没用的感觉(或者充其量只是一个简单的语法便利性,提供零性能值).
我们以此代码为例:
int[] myInts = new int[]{1,2,3,5,8,13,21};
IntStream myIntStream = IntStream.of(myInts);
int[] myChangedArray = myIntStream
.peek(n -> System.out.println("About to square: " + n))
.map(n -> (int)Math.pow(n, 2))
.peek(n -> System.out.println("Done squaring, result: " + n))
.toArray();
Run Code Online (Sandbox Code Playgroud)
这将登录控制台,因为terminal operation在这种情况下toArray()会调用,而且我们的流是惰性的,只有在调用终端操作时才会执行.当然我也可以这样做:
IntStream myChangedInts = myIntStream
.peek(n -> System.out.println("About to square: " + n))
.map(n -> (int)Math.pow(n, 2))
.peek(n -> System.out.println("Done squaring, result: " + n));
Run Code Online (Sandbox Code Playgroud)
什么都不打印,因为地图没有发生,因为我不需要数据.直到我称之为:
int[] myChangedArray = myChangedInts.toArray();
Run Code Online (Sandbox Code Playgroud)
瞧,我得到了我的映射数据,以及我的控制台日志.除了我看到它没有任何好处.我意识到我可以在调用之前很久就定义过滤器代码toArray(),并且我可以传递这个"未经过实际过滤的流",但那又是什么?这是唯一的好处吗?
这些文章似乎意味着与懒惰相关的性能提升,例如:
在Java 8 Streams API中,中间操作是惰性的,并且其内部处理模型已经过优化,使其能够以高性能处理大量数据.
和
Java 8 Streams API借助于短路操作优化了流处理.短路方法一旦满足条件就结束流处理.在通常的短路操作中,一旦满足条件,就会中断所有处于管道之前的中间操作.一些中间操作和终端操作具有此行为.
这听起来像是从一个循环中脱离出来,而根本就与懒惰无关.
最后,第二篇文章中有一条令人困惑的界线:
懒惰的操作可以提高效率.这是一种不处理陈旧数据的方法.在逐渐消耗输入数据而不是事先拥有完整的元素集的情况下,延迟操作可能很有用.例如,考虑使用Stream#generate(Supplier <T>)创建无限流的情况,并且提供的Supplier函数逐渐从远程服务器接收数据.在这种情况下,只有在需要时才会在终端操作中进行服务器调用.
不处理陈旧数据?什么?延迟加载如何阻止某人处理陈旧数据?
TLDR:除了能够在以后运行filter/map/reduce /无论什么操作(它提供零性能优势)之外,延迟加载是否有任何好处?
如果是这样,那么真实世界的用例是什么?
ern*_*t_k 16
您的终端操作toArray()可能支持您的参数,因为它需要流的所有元素.
有些终端操作没有.对于这些,如果流不是懒惰的执行将是一种浪费.两个例子:
//example 1: print first element of 1000 after transformations
IntStream.range(0, 1000)
.peek(System.out::println)
.mapToObj(String::valueOf)
.peek(System.out::println)
.findFirst()
.ifPresent(System.out::println);
//example 2: check if any value has an even key
boolean valid = records.
.map(this::heavyConversion)
.filter(this::checkWithWebService)
.mapToInt(Record::getKey)
.anyMatch(i -> i % 2 == 0)
Run Code Online (Sandbox Code Playgroud)
第一个流将打印:
0
0
0
Run Code Online (Sandbox Code Playgroud)
也就是说,中间操作将仅在一个元素上运行.这是一项重要的优化.如果它不是懒惰的,那么所有的peek()调用都必须在所有元素上运行(完全没必要,因为你只对一个元素感兴趣).中间操作可能很昂贵(例如在第二个例子中)
短路终端操作(其中toArray没有)使得这种优化成为可能.
map().reduce()你是对的, or不会有任何好处,但是, ,等map().collect()有一个非常明显的好处。基本上,任何可以短路的操作。findAny() findFirst()anyMatch()allMatch()
惰性对于您的API用户非常有用,尤其是当Stream管道评估的最终结果可能非常大时!
一个简单的示例是Files.linesJava API本身中的方法。如果您不想将整个文件读入内存,而只需要前几N行,则只需编写:
Stream<String> stream = Files.lines(path); // lazy operation
List<String> result = stream.limit(N).collect(Collectors.toList()); // read and collect
Run Code Online (Sandbox Code Playgroud)
我在我们的代码库中有一个真实的例子,因为我要简化它,不完全确定你可能喜欢它还是完全掌握它...
我们有一个需要的服务List<CustomService>,我想称之为.现在为了调用它,我要去一个数据库(比现实简单得多)并获得一个List<DBObject>; 为了从中获得List<CustomService>,需要进行一些重大变革.
这是我的选择,转换到位并通过列表.很简单,但可能不是那么理想.第二种选择,重构服务,接受a List<DBObject>和a Function<DBObject, CustomService>.这听起来微不足道,但它可以使懒惰(除其他外).该服务有时可能只需要该列表中的一些元素,或者有时只需要max一些属性等等 - 因此我不需要对所有元素进行重大转换,这就是Stream API基于拉动的懒惰是赢家的地方.
在Streams存在之前,我们曾经使用过guava.它也Lists.transform( list, function)很懒惰.
这不是溪流的基本特征,即使没有番石榴也可以做到,但这种方式更简单.这里提供的例子findFirst很棒,最容易理解; 这是懒惰的整个点,元素只在需要时被拉动,它们不是从一个中间操作传递到另一个中,而是一次从一个阶段传递到另一个阶段.