Bootstrap

java8新特性-流-stream()和parallelStream()

在 Java 8 中, 集合接口有两个方法来生成流:

stream() − 为集合创建串行流。
parallelStream() − 为集合创建并行流。

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal
operation)得到前面处理的结果。
在这里插入图片描述在这里插入图片描述

Stream(流)是一个来自数据源的元素队列并支持聚合操作
元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

和以前的Collection操作不同, Stream操作还有两个基础的特征:
Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。
这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。
Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

2.介绍玩stream的概念之后,我们接下来以举例的方式来进行说明(parallelStream概念差不多和stream一样)

1.举例说明

有一个集合:

List<User> users = getList(); 

现在想获取User的角色;在后续的逻辑处理中使用;

第一种方法,用for循环:
复制代码

//定义一个集合存放用户角色

List<String> role = new ArrayList<String>();

for(int i=0;i<users.size();i++){

  role .add(users.get(i).getRole());

}

复制代码

这种方法要写好几行代码,有没有简单点的,有,java8 API能一行搞定:

第二种方法:用stream代替for或者foreach循环

List<String> role = users.stream().map(User::getRole).collect(Collectors.toList())

解释下一这行代码:

users:一个实体类的集合,类型为List
User:实体类
getRole:实体类中的get方法,为获取User的角色

stream().map(A::B).collect.(Collectors.toList()).contain("**")用法
 //获取所有用户列表信息

List<Role> roleList = UserUtils.getUser().getRoleList();

Boolean flag = roleList.stream().map(Role::getEnname).collect(Collectors.toList()).contains("James");

解释一下,上一句的含义:

//1.将用户信息列表转化为流的形式(用stream 代替了for和foreach循环)
//2.以map的数据格式获取所有用户的Enname
//3.然后把所有的Enname放到一个collect集合中,然后转为List类型
//4.最后判断该List中是否包含字符串"James"

3.Stream的特性和优点

stream()优点:无存储。对大数据量的集合的循环处理,stream拥有极大的优势,完全可以用stream去代替for循环。

stream()介绍:是java对集合操作的优化,相较于迭代器,使用Stream的速度非常快,并且它支持并行方式处理集合中的数据,默认情况能充分利用cpu的资源。同时支持函数式编程,代码非常简洁。

Stream是一种用来计算数据的流,它本身并没有存储数据。你可以认为它是对数据源的一个映射或者视图。

Stream的工作流程是:获取数据源->进行一次或多次逻辑转换操作->进行归约操作形成新的流(最后可以将流转换成集合)。

stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。
        为函数式编程而生。对stream的任何修改都不会修改背后的数据源,比如对stream执行过滤操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新stream。
        惰式执行:stream上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行。
        可消费性:stream只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成。

中间操作惰性执行:一个流后面可以跟随0到多个中间操作,主要目的是打开流,并没有真正的去计算,而是做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历,并没有消耗资源。
还有多个中间操作的话,这里的时间复杂度并不是n个for循环,转换操作都是 lazy 的,多个转换操作只会在 Terminal
操作的时候融合起来,一次循环完成。可以这样简单的理解,Stream
里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在Terminal操作的时候循环 Stream
对应的集合,然后对每个元素执行所有的函数。

流的末端操作只能有一次: 当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。之后如果想要操作就必须新打开流。

4.关于流被关闭不能再操作的异常:
这里曾经遇到过一个错误:

stream has already been operated upon or closed

意思是流已经被关闭了,这是因为当我们使用末端操作之后,流就被关闭了,无法再次被调用,如果我们想重复调用,只能重新打开一个新的流。

5.性能比较

如果数据在1万以内的话,for循环效率高于foreach和stream;如果数据量在10万的时候,stream效率最高,其次是foreach,最后是for。

另外需要注意的是如果数据达到100万的话,parallelStream异步并行处理效率最高,高于foreach和for。

6.stream的一些常用方法
6.1.stream用法之排序

sorted提供了2个接口:
1、sorted() 默认使用自然序排序, 其中的元素必须实现Comparable 接口 。
2、sorted(Comparator<? super T> comparator) :我们可以使用lambada 来创建一个Comparator 实例。可以按照升序或着降序来排序元素。
注意sorted是一个有状态的中间操作,即,只有全部执行完所有的数据才能知道结果。

比如:将一些字符串在地址中按出现的顺序排列:
复制代码

public static void main(String[] args){
  String address = "门前大桥下游过一群鸭";
  List<String> list= new ArrayList<>();
  list.add("一群鸭");
  list.add("大桥下");
  list.add("门前");
  list.add("游过");
  list.add("我家");
  list.stream().sorted(
          Comparator.comparing(a->address.indexOf(a))).forEach(System.out :: println);
}

可以不使用比较器:

 //按地址由大到小排序
  list.stream().sorted(
        (a,b)->address.IndexOf(a)-address.IndexOf(b)).forEach(System.out :: println);

运行结果:

我家
门前
大桥下
游过
一群鸭

Process finished with exit code 0

注:1.“我家”
这个字段在地址定义中并没有出现,但是在list中添加了,所以序号是-1(默认序号-1),被排在最前面,其他字段按address中的地址大小,从大到小排序

6.2.Comparator.comparing();这个是比较器提供的一个方法,它返回的也是一个比较器,源码如下:

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor)
    {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
    }

6.4stream用法之筛选

上一步中,我们把一些字符串,按照在地址中出现的顺序排序。

接下来我们可能想要进行筛选,把不在地址中,但是indexof为“-1”,排在最前面的数据筛选掉:

filter可以对集合进行筛选,它的参数可以是一个lambda表达式,流中的数据将会通过该lambda表达式返回新的流。

这里Stream有一个特性很重要,它像一个管道,可以将多个操作连接起来,并只执行一次for循环,这样大大提高了效率,即使第二次的流操作需要第一次流操作的结果,时间复杂度也只有一个for循环:

于是我可以在前面加个filter(),这样把“-1”过滤掉: 即把"我家"过滤掉

  String address = "门前大桥下游过一群鸭";
  List<String> list= new ArrayList<>();
  list.add("一群鸭");
  list.add("大桥下");
  list.add("门前");
  list.add("游过");
  list.add("我家");

list.stream().filter(a->address.indexOf(a)!=-1).sorted( Comparator.comparing(a->address.indexOf(a)) ).forEach(System.out :: println);

运行结果:

门前
大桥下
游过
一群鸭

Process finished with exit code 0

注:foreach是一个终端操作,参数也是一个函数,它会迭代中间操作完成后的每一个数据,这里它将每个不为空的元素打印出来。

其它的过滤操作还包括:

limit(long maxSize):获得指定数量的流。

distinct():通过hashCode和equals去除重复元素。

6.5stream用法之映射

映射操作,就像一个管道,可以将流中的元素通过一个函数进行映射,返回一个新的元素。

这样遍历映射,最终返回一个新的容器,注意:这里返回的新容器数据类型可以不与原容器类型相同:

举个例子:我们将address中每个元素的位置找出,并返回一个int类型的存储位置信息的数组:

public static void main(String[] args){
  String address = "门前大桥下游过一群鸭";
  List<String> list= new ArrayList<>();
  list.add("一群鸭");
  list.add("大桥下");
  list.add("门前");
  list.add("游过");
  list.add("我家");
  List<Integer> aIntegers =list.stream()
          .map(str->mapPrintIndex(address, str)).collect(Collectors.toList());
  System.out.println(aIntegers);
  }
  private static int mapPrintIndex(String address,String str) {
    return address.indexOf(str);
  }

运行结果:

[7, 2, 0, 5, -1]

Process finished with exit code 0

map():用于映射每个元素到对应的结果。以下代码片段使用 map 输出了元素对应的平方数:

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// 获取对应的平方数
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;