Java8 Stream API理解与总结

问题场景描述

最近接到这样一个开发需求:每天0点 从第三方数据库读取业务数据,并对每条记录处理和过滤后进行持久化记录,数据量在百万级别,要求速度要尽可能快。所以涉及到逐条记录的迭代以及对这条记录有过滤和逻辑处理,以及对速度的要求,首先想到了前段时间天看过的java8 Stream API来做。于是详细看了相关资料后对java 8这一特性作一些总结和思考。

理解Stream

Stream是什么

java官方文档中这样描述Stream:

A sequence of elements supporting sequential and parallel aggregate operations.

这句话可以理解为:

  • Stream是元素的集合,这点让Stream看起来用些类似Iterator;
  • 可以支持顺序和并行的对原Stream进行汇聚的操作;

通俗点讲可以把Stream当成一个高级版本的Iterator。原始版本的Iterator,用户只能一个一个的遍历元素并对其执行某些操作;高级版本的Stream,用户只要给出需要对其包含的元素执行什么操作,比如“过滤掉长度大于10的字符串”、“获取每个字符串的首字母”等,具体这些操作如何应用到每个元素上,就给Stream就好了!

这里体现除了函数式编程的思想,相对于相对于命令式编程更体现了结果>过程的编程思想。以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式实现。

为什么使用Stream

Stream API引入的目的在于弥补Java函数式编程的缺陷。对于很多支持函数式编程的语言,map()、reduce()基本上都内置到语言的标准库中了,不过,Java 8的Stream API总体来讲仍然是非常完善和强大,足以用很少的代码完成许多复杂的功能。

创建一个Stream有很多方法,最简单的方法是把一个Collection变成Stream。我们来看最基本的几个操作:

public static void main(String[] args) {
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Stream<Integer> stream = numbers.stream();
    stream.filter((x) -> {
        return x % 2 == 0;
    }).map((x) -> {
        return x * x;
    }).forEach(System.out::println);
}

集合类新增的stream()方法用于把一个集合变成Stream,然后,通过filter()、map()等实现Stream的变换。Stream还有一个forEach()来完成每个元素的迭代。

为什么不在集合类实现这些操作,而是定义了全新的Stream API?Oracle官方给出了几个重要原因:

一是集合类持有的所有元素都是存储在内存中的,非常巨大的集合类会占用大量的内存,而Stream的元素却是在访问的时候才被计算出来,这种“延迟计算”的特性有点类似Clojure的lazy-seq,占用内存很少。

二是集合类的迭代逻辑是调用者负责,通常是for循环,而Stream的迭代是隐含在对Stream的各种操作中,例如map()。

Stream怎么用

基本语法

这里写图片描述

图片就是对于Stream例子的一个解析,可以很清楚的看见:原本一条语句被三种颜色的框分割成了三个部分。红色框中的语句是一个Stream的生命开始的地方,负责创建一个Stream实例;绿色框中的语句是赋予Stream灵魂的地方,把一个Stream转换成另外一个Stream,红框的语句生成的是一个包含所有nums变量的Stream,进过绿框的filter方法以后,重新生成了一个过滤掉原nums列表所有null以后的Stream;蓝色框中的语句是丰收的地方,把Stream的里面包含的内容按照某种算法来汇聚成一个值,例子中是获取Stream中包含的元素个数。

使用Stream的基本步骤:

  • 创建Stream;
  • 转换Stream,每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换);
  • 对Stream进行聚合(Reduce)操作,获取想要的结果;

创建

1、通过Stream接口的静态工厂方法(注意:Java8里接口可以带静态方法);

javadoc一共提供了3种创建Stream的方法,有of方法、generator方法、 iterate方法。
下面为of方法代码,有两个overload方法,一个接受变长参数,一个接口单一值

Stream<Integer> integerStream = Stream.of(1, 2, 3, 5);
Stream<String> stringStream = Stream.of("taobao");
2、通过Collection接口的默认方法

参照本文中第一个代码示例。

转换

流处理方式都给我们提供了很多便利的接口,比如:
1、filter接口,对整个集合进行过滤

Stream filter(Predicate predicate);

2、map接口,将整个集合转换成新对象,对应原始类型有mapToInt、mapToLong、mapToDouble

 Stream map(Function mapper);

3、distinct接口,与sql里的distinct基本一样,去重

Stream distinct();

4、sorted,快速的对集合进行排序

Stream sorted();

5、forEach接口,对整个集合进行遍历

void forEach(Consumer action);

等等,还有一些其他的函数,如对一个Stream进行截断操作的limit、返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream的skip、生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数的peek等。

聚合

关于聚合有一种比较特殊的可变聚合,就是把聚合结果放到一个新的集合中去,代码如下:

List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).collect(Collectors.toList());

剩下的聚合方式类似我们sql中的分组聚合了,比如:max和min:使用给定的比较器(Operator),返回Stream中的最大最小值、 count方法:获取Stream中元素的个数等。

ParallelStream

什么是ParallelStream

那么什么是parallelStream呢?其源码中写道:

Returns a possibly parallel {@code Stream} with this collection as its
source. It is allowable for this method to return a sequential stream.

可见ParallelStream是个并行的Stream,也就是说它是一个多线程的Stream,并且允许返回一个顺序流。

这里需要注意的是Stream是一个串行流,他返回的集合处理结果使是有顺序的,而
ParallelStream是一个并行流,他是把集合分片给不同的进程去处理所以返回的结果是无序的,就forEach()这个操作來讲,如果平行处理时,希望最后顺序是按照原来Stream的数据顺序,那可以调用forEachOrdered()。

parallelStream的陷阱

查看了网上的资料总结如下:
1、parallelStream是创建一个并行的Stream,而且他的并行操作是不具备线程传播性的,所以在使用会话管理器的时候是无法获取值的.
原文地址:https://www.jianshu.com/p/5a49b10f3cfd
2、存在线程阻塞问题
原文地址:https://www.cnblogs.com/imyijie/p/4478074.html

怎么正确使用parallelStream

上面我们也看到了parallelStream所带来的隐患和好处,那么,在从stream和parallelStream方法中进行选择时,我们可以考虑以下几个问题:

  1. 是否需要并行?
  2. 任务之间是否是独立的?是否会引起任何竞态条件?
  3. 结果是否取决于任务的调用顺序?

对于问题1,在回答这个问题之前,你需要弄清楚你要解决的问题是什么,数据量有多大,计算的特点是什么?并不是所有的问题都适合使用并发程序来求解,比如当数据量不大时,顺序执行往往比并行执行更快。毕竟,准备线程池和其它相关资源也是需要时间的。但是,当任务涉及到I/O操作并且任务之间不互相依赖时,那么并行化就是一个不错的选择。通常而言,将这类程序并行化之后,执行速度会提升好几个等级。

对于问题2,如果任务之间是独立的,并且代码中不涉及到对同一个对象的某个状态或者某个变量的更新操作,那么就表明代码是可以被并行化的。

对于问题3,由于在并行环境中任务的执行顺序是不确定的,因此对于依赖于顺序的任务而言,并行化也许不能给出正确的结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值