炼数成金 门户 大数据 Java 查看内容

使用Java Stream API提高编程效率

2018-11-16 10:16| 发布者: 炼数成金_小数| 查看: 16092| 评论: 0|原作者: 黄明远|来自: AI脑力波

摘要: Stream API 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。它是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作或者大批 ...

Java 测试 Hadoop 编程 高性能 方法

前言
Stream API 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。它是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作或者大批量数据操作。Stream API 借助于 Lambda 表达式,极大地提高了编程效率和程序可读性。同时它提供串行和并行两种模式进行聚合操作,并发模式能够充分利用多核处理器的优势。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。

使用Stream API可大幅度减少集合操作代码,逻辑更加清晰,极大减少代码出现BUG的几率。


Stream API介绍
一、 什么是Stream
Stream是对集合算法和迭代计算的封装,用户不再需要对集合数据进行for循环遍历操作,只需给出对集合中的元素执行什么操作,比如“过滤”、“转换”、“查找”等,Stream就会隐式地在内部进行遍历,执行相应的数据变换操作。

二、 Stream的构成
Stream流类似于一个pipeline,使用时,先从源数据生成流,再进行数据变换,最后生成最终结果。每次转换,原有的Stream不变,重新生成一个新的Stream,因此允许操作可以像链条一样串联起来变成一个管道,其示意图如下所示:
 
三、 Stream的生成
有多种方式可生成流:
从集合中生成
1、 Collection.stream()方式
Collection可以是List、Set等集合。

2、 Collection.parallelStream()方式
Collection可以是List、Set等集合,生成并行处理流。

3、 Arrays.stream()或者Stream.of()
从数据或者参数列表中生成流。

静态方法生成
1、 IntStream.range()
产生某个范围内的数据流。

2、 Files.walk()
生成文件列表流。

生成无限数据流
1、 Random.ints()
生成随机数据流,该流没有边界,产生无限的随机数,一般通过limit方法限制使用。

2、 BitSet.stream()


四、 Stream的特性
1、 性能更好
相比自行编写多个for循环对数据进行处理,Stream把对集合中的元素进行变换的操作集中的一个操作集中,在一次循环操作中对单个元素执行所有操作。时间复杂度为O(n)。另外,流通过并行操作把数据切分成多个小段,通过多线程方式执行,性能更好。

2、 惰性操作
对流的操作可分为两种:中间操作和结束操作。
二者的特点是:中间操作总会惰性执行,而结束操作时才触发真实的计算。中间操作只会生成一个标记了该操作的新Stream,在结束操作时把所有积攒的中间操作以pipeline的方式执行,这样可以减少迭代次数。

Ø 中间操作方法包括:concat、distinct、filter、map、flatMap、limit、peek、skip、sorted、parallel等

Ø 结束操作方法包括:allMatch、anyMatch、noneMatch、collect、count、findAny、findFirst、forEach、max、min、reduce、toArray等

区分某个方法是中间操作还是结束操作方法可以查看该方法的返回值,返回值为Stream的则为中间操作,否则为结束操作。


大幅度减少Java代码
使用Stream API可大幅度减少程序代码。以一个使用Java代码查询用户金融交易的交易ID的代码片段举例,查找贷款类型的用户交易,并对交易进行顺序排序,然后返回排序的交易ID列表,使用for循环的代码片段如下:

List<Transaction> userTransactions = new Arraylist<>();
        for(Transaction t: transactions){
            if(t.getType() == Transaction.TYPE_LOAN){
                userTransactions.add(t);
            }
        }        
        Collections.sort(userTransactions, new Comparator(){
            public int compare(Transaction t1, Transaction t2){
                return t2.getValue().compareTo(t1.getValue());
            }
        });        
        List<Integer> transactionIds = new ArrayList<>();
        for(Transaction t: groceryTransactions){
            transactionsIds.add(t.getId());
        }        
        return transactionIds;
 
而以上代码改为使用Stream API实现代码如下:
return transactions.parallelStream().
                filter(t -> t.getType() == Transaction.TYPE_LOAN).
                sorted(comparing(Transaction::getValue).reversed()).
                map(Transaction::getId).
                collect(toList());

对比可知,使用Stream API实现的代码量仅为原来的三分之一。新的代码逻辑也更加紧凑、更加清晰。更少的代码量,往往意味着更少的BUG。
不接触线程实现高性能并发编程

Stream API让并行程序的编程变得从未有的简单。仅仅通过一个parallelStream方法即实现了并行计算。如上例中的程序:
return transactions.parallelStream().
                filter(t -> t.getType() == Transaction.TYPE_LOAN).
                sorted(comparing(Transaction::getValue).reversed()).
                map(Transaction::getId).
                collect(toList());

使用parallelStream,流就具备了自动适配系统上的CPU数量,生成对应数量的线程,并行执行流上的中间操作,lambda表达式自动被调度到不同的线程上进行并行处理,最终将结果进行reduce合并后返回。

整个代码逻辑中,没有任何的多线程操作,完全不接触、不操作线程,即实现了线程安全的高性能并发编程。


函数式编程
使用函数式编程写出的代码非常简洁且友好,函数式编程与Stream API结合可实现功能强大且非常优雅的代码逻辑。

1、 forEach遍历
forEach方法接收一个Lambda表达式,然后在Stream的每一个元素上执行该表达式。

Stream<String> stream= Stream.of("M", "S", "X", "F");
stream.forEach(str -> System.out.println(str));

2、 filter过滤
filter方法对原始Stream上的元素进行某项测试,通过测试的元素被保留下来生成一个新的Stream。

Stream<String> stream= Stream.of("M", "S", "X", "F");
stream.filter(str -> !str.equals(“X”));

3、 map转换
map方法的作用是把原始Stream上的每一个元素映射成新Stream中的另外一个元素。通过这种转换,流中的数据就改变了。

Stream<String> stream= Stream.of("M", "S", "X", "F");
stream.map(String::toUpperCase);

4、 sorted排序
sorted方法对Stream中的数据进行排序,排序策略由用户定义,它比数组排序强大的地方在于可以首先对Stream中的数据进行各种filter、map之后再进行排序,这可以把排序范围缩小到最终结果集合上,这点可以显著地减少排序时间,提高排序效率。

Stream<String> stream= Stream.of("M", "S", "X", "F");
stream.map(String::toUpperCase).sorted((s1,s2)->s1.compareTo(s2));

5、 collect返回结果
collect可根据不同的结果收集策略返回不同的结果集合。

Ø 返回List
将Stream中的元素转换成List返回。
stream.collect(Collectors.toList());

Ø 返回分组groupingBy
按照某个元素字段进行分组。
Stream<String> stream= Stream.of("M", "S", "X", "F", "m", "s", "x", "f",);
stream.collect(Collectors.groupingBy(String::toLowerCase));


避免空指针异常
Stream流中使用findFirst等返回单个值的结束操作方法,则返回的类型为Optional,Optional返回值的可读性更好,该类型可显著地避免NullPointerException空指针异常。

使用Optional,程序无须再进行返回值的if (value == null)判断,代码可读性也更好,而且它提供编译期检查,促使程序员在编码阶段就能正确地处理空指针问题,而不是把问题留到运行期。

Stream中的findFirst、findAny、min、max、reduce等方法均返回Optional类型。因而,使用Stream有效地避免了指针判断,避免了NPE问题。

Stream<String> stream= Stream.of("M", "S", "X", "F", "m", "s", "x", "f",);
stream.findFirst().ifPresent(System.out::println);

Stream的重要特性
1、 Stream没有内部存储,只是从数据源中获取数据,操作后输出。
2、 不会修改底层数据结构,而是重新生成一个新的Stream。例如filter不会从源数据中删除元素。
3、 所有Stream的操作必须是Lambda表达式。
4、 中间操作为惰性访问,性能非常好。
5、 不用编写多线程代码,只要使用了parallelStream,所有操作都是自动并行处理的。


总结
本文介绍了Stream API的优势及使用方法,随着Java的发展,不管是从语言层面还是API层面都很好地借鉴了其他语言的特点,这些改进点极大地优化了我们的编程体验。Stream API就是其中一个重大的改进,合理地使用Stream API,可以让我们的工作效率更高。

声明:文章收集于网络,如有侵权,请联系小编及时处理,谢谢!

欢迎加入本站公开兴趣群
软件开发技术群
兴趣范围包括:Java,C/C++,Python,PHP,Ruby,shell等各种语言开发经验交流,各种框架使用,外包项目机会,学习、培训、跳槽等交流
QQ群:26931708

Hadoop源代码研究群
兴趣范围包括:Hadoop源代码解读,改进,优化,分布式系统场景定制,与Hadoop有关的各种开源项目,总之就是玩转Hadoop
QQ群:288410967 
1

鲜花

握手

雷人

路过

鸡蛋

刚表态过的朋友 (1 人)

相关阅读

最新评论

热门频道

  • 大数据
  • 商业智能
  • 量化投资
  • 科学探索
  • 创业

即将开课

 

GMT+8, 2018-12-11 06:12 , Processed in 0.184602 second(s), 25 queries .