今日主题:流式操作

简介

现在来看看流式操作吧,JDK8的流式操作是真的香啊,你还不会你就out了

环境

  • JDK8

介绍

jdk8引进来的lambda语法是新语法,里面融进了python和c#的部分语法,stream()函数是主要用于集合而言的,对于数组就没效果

学习Stream之前,首先要了解他的操作符包含中间操作符和终止操作符

中间操作符

对于数据流来说,中间操作符在执行制定处理程序后,数据流依然可以传递给下一级的操作符。

中间操作符包含8种(排除了parallel,sequential,这两个操作并不涉及到对数据流的加工操作):

map(mapToInt,mapToLong,mapToDouble) 转换操作符,把比如A->B,这里默认提供了转int,long,double的操作符。flatmap(flatmapToInt,flatmapToLong,flatmapToDouble) 拍平操作比如把 int[]{2,3,4} 拍平 变成 2,3,4 也就是从原来的一个数据变成了3个数据,这里默认提供了拍平成int,long,double的操作符。limit 限流操作,比如数据流中有10个 我只要出前3个就可以使用。distint 去重操作,对重复元素去重,底层使用了equals方法。filter 过滤操作,把不想要的数据过滤。peek 挑出操作,如果想对数据进行某些操作,如:读取、编辑修改等。skip 跳过操作,跳过某些元素。sorted(unordered) 排序操作,对元素排序,前提是实现Comparable接口,当然也可以自定义比较器。

终止操作符

数据经过中间加工操作,就轮到终止操作符上场了;终止操作符就是用来对数据进行收集或者消费的,数据到了终止操作这里就不会向下流动了,终止操作符只能使用一次。

collect 收集操作,将所有数据收集起来,这个操作非常重要,官方的提供的Collectors 提供了非常多收集器,可以说Stream 的核心在于Collectors。count 统计操作,统计最终的数据个数。findFirst、findAny 查找操作,查找第一个、查找任何一个 返回的类型为Optional。noneMatch、allMatch、anyMatch 匹配操作,数据流中是否存在符合条件的元素 返回值为bool 值。min、max 最值操作,需要自定义比较器,返回数据流中最大最小的值。reduce 规约操作,将整个数据流的值规约为一个值,count、min、max底层就是使用reduce。forEach、forEachOrdered 遍历操作,这里就是对最终的数据进行消费了。toArray 数组操作,将数据流的元素转换成数组。

这里只介绍了Stream,并没有涉及到IntStream、LongStream、DoubleStream,这三个流实现了一些特有的操作符,我将在后续文章中介绍到。

说了这么多,只介绍这些操作符还远远不够;俗话说,实践出真知。那么,Let‘s go。

1、Stream 流概念

Stream 流的使用总是按照一定的步骤进行,可以抽象出下面的使用流程。

数据源(source) -> 数据处理/转换(intermedia) -> 结果处理(terminal )

1.1、数据源

数据源(source)也就是数据的来源,可以通过多种方式获得 Stream 数据源,下面列举几种常见的获取方式。

Collection.stream(); 从集合获取流。
Collection.parallelStream(); 从集合获取并行流。
Arrays.stream(T array) or Stream.of(); 从数组获取流。
BufferedReader.lines(); 从输入流中获取流。
IntStream.of() ; 从静态方法中获取流。
Stream.generate(); 自己生成流

1.2、数据处理

数据处理/转换(intermedia)步骤可以有多个操作,这步也被称为intermedia(中间操作)。在这个步骤中不管怎样操作,它返回的都是一个新的流对象,原始数据不会发生任何改变,而且这个步骤是惰性计算处理的,也就是说只调用方法并不会开始处理,只有在真正的开始收集结果时,中间操作才会生效,而且如果遍历没有完成,想要的结果已经获取到了(比如获取第一个值),会停止遍历,然后返回结果。惰性计算可以显著提高运行效率。

 //将数组转成集合
        List<String> nameList= Arrays.asList("aaf","bbccdd","cdsc","dsd");

        /*1、筛选名字长度为4的
        * 2、名字前面拼接This is
        * 3、遍历输出
        * */
        nameList.stream()
                .filter(name->name.length()==4)
                .map(name->"This is "+name)
                .forEach(name-> System.out.println(name));

数据处理/转换操作自然不止是上面演示的过滤 filter 和 map映射两种,另外还有 map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered 等。

1.3、收集结果

结果处理(terminal )是流处理的最后一步,执行完这一步之后流会被彻底用尽,流也不能继续操作了。也只有到了这个操作的时候,流的数据处理/转换等中间过程才会开始计算,也就是上面所说的惰性计算。结果处理也必定是流操作的最后一步。

常见的结果处理操作有 forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator 等。

//数组转成集合
        List<String> nameList = Arrays.asList("Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter");
        //将集合的元素变成大写的
        List<String> upperCaseName=nameList.stream()
                .map(String::toUpperCase)
                .collect(Collectors.toList());
        upperCaseName.forEach(name-> System.out.println(name));

1.4、short-circuiting

有一种 Stream 操作被称作 short-circuiting ,它是指当 Stream 流无限大但是需要返回的 Stream 流是有限的时候,而又希望它能在有限的时间内计算出结果,那么这个操作就被称为short-circuiting。例如 findFirst 操作。

2、Stream流的使用

Stream 流在使用时候总是借助于 Lambda 表达式进行操作,Stream 流的操作也有很多种方式,下面列举的是常用的 11 种操作。

2.1、Stream流的获取

大概是以下几种获取流的方式

    public static void createStream() throws FileNotFoundException {
        //数组转集合
        List<String> nameList = Arrays.asList("Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter");
        //数组
        String[] nameArr = {"Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter"};
        //集合获取串行Stream流
        Stream<String> nameListStream=nameList.stream();
        //集合获取并行Stream流
        Stream<String> nameListStream2=nameList.parallelStream();
        //数组获取Stream流
        Stream<String> nameAttrStream=Stream.of(nameArr);
        //数组获取Stream流
        Stream<String> nameAttrStream2=Arrays.stream(nameArr);
        //文件流获取Stream流
        BufferedReader bufferedReader=new BufferedReader(new FileReader("1.txt"));
        Stream<String> linesStream=bufferedReader.lines();
        //从静态方法获取流操作
        IntStream rangeStream=IntStream.range(1,10);
        rangeStream.limit(10).forEach(num-> System.out.println(num+","));
        
    }

2.2、forEach

遍历

List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
numberList.stream().forEach(number -> System.out.println(number+","));

2.3、map/ flatMap

使用 map 把对象一对一映射成另一种对象或者形式。相当于:key是原来的值,value是处理之后的值 map是一对一映射

 //数组转集合
        List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        //映射成2倍数字
        List<Integer> collect=numberList.stream().map(num->num*2).collect(Collectors.toList());
        collect.forEach(num-> System.out.println(num));

有时候会出现一对多的映射,这时候就用flatmap

2.4、filter

使用 filter 进行数据筛选,挑选出想要的元素,下面的例子演示怎么挑选出偶数数字。

 //数组转集合
        List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        //筛选偶数数字
        List<Integer> collect=numberList.stream().filter(num->num%2==0).collect(Collectors.toList());
        collect.forEach(num-> System.out.println(num));

2.5、findFirst

流式操作真香啊_编程

 

findFirst 可以查找出 Stream 流中的第一个元素,它返回的是一个 Optional 类型

List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        Optional<Integer> firstNumber=numberList.stream().findFirst();
        //orElse表示如果结果为空就用-1表示
        System.out.println(firstNumber.orElse(-1));

findFirst 方法在查找到需要的数据之后就会返回不再遍历数据了,也因此 findFirst 方法可以对有无限数据的 Stream 流进行操作,也可以说 findFirst 是一个 short-circuiting 操作。

2.6、collect / toArray

Stream 流可以轻松的转换为其他结构

//stream转换为其他数据结构
        List<Integer> numberList=Arrays.asList(1,2,3,4,5);
        //to array
        Integer[] toArray=numberList.stream().toArray(Integer[]::new);
        System.out.println(toArray);
        //to List
        List<Integer> integerList=numberList.stream().collect(Collectors.toList());
        System.out.println(integerList);
        //to set
        Set<Integer> integerSet=numberList.stream().collect(Collectors.toSet());
        System.out.println(integerSet);
        //to String
        String toString=numberList.stream().map(num->String.valueOf(num)).collect(Collectors.joining());
        System.out.println("toString:"+toString);
        //to String split by ,
        String toString2=numberList.stream().map(num->String.valueOf(num)).collect(Collectors.joining(","));
        System.out.println("toString2:"+toString2);

2.7、limit / skip

limit:获取前面多少个元素 skip:跳过前面多少个元素

// 生成自己的随机数流
        List<Integer> ageList = Arrays.asList(11, 22, 13, 14, 25, 26);
        //取前面前3个
        ageList.stream().limit(3).forEach(System.out::println);
        //跳过前面3个
        ageList.stream().skip(3).forEach(System.out::println);

2.8、Statistics

数学统计功能,求一组数组的最大值、最小值、个数、数据和、平均数等。

//数学计算测试
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
        //mapToInt是将元素映射成int类型
        IntSummaryStatistics stats=list.stream().mapToInt(x->x).summaryStatistics();
        System.out.println("最大值:"+stats.getMax());
        System.out.println("最小值:"+stats.getMin());
        System.out.println("个数:"+stats.getCount());
        System.out.println("和:"+stats.getSum());
        System.out.println("平均数:"+stats.getAverage());

2.9、groupingBy

分组聚合功能,和数据库的 Group by 的功能一致。

List<Integer> ageList=Arrays.asList(11,12,13,22,23,24);
        Map<String,List<Integer>> ageGroupMap=ageList.stream().collect(Collectors.groupingBy(age->String.valueOf(age/10)));
        ageGroupMap.forEach((k,v)->{
            System.out.println("年龄:"+k+"0多岁的有:"+v);
        });

2.10、partitioningBy

按某个条件分组 给一组年龄,分出成年人和未成年人

//按某个条件分组,给一组年龄,分出成年人和未成年人
        List<Integer> ageList=Arrays.asList(12,23,45,17,58,35);
        Map<Boolean,List<Integer>> ageMap=ageList.stream().collect(Collectors.partitioningBy(age->age>18));
        System.out.println("未成年人:"+ageMap.get(false));
        System.out.println("成年人:"+ageMap.get(true));

2.11、进阶 - 自己生成 Stream 流

// 生成自己的随机数流
        Random random = new Random();
        Stream<Integer> generateRandom = Stream.generate(random::nextInt);
        generateRandom.limit(5).forEach(System.out::println);
        // 生成自己的 UUID 流
        Stream<UUID> generate = Stream.generate(UUID::randomUUID);
        generate.limit(5).forEach(System.out::println);

3、并行计算

获取 Stream 流时可以使用 parallelStream 方法代替 stream 方法以获取并行处理流,并行处理可以充分的发挥多核优势,而且不增加编码的复杂性。

/**
  * 并行计算
  */
 @Test
 public void main() {
     // 生成自己的随机数流,取一千万个随机数
     Random random = new Random();
     Stream<Integer> generateRandom = Stream.generate(random::nextInt);
     List<Integer> numberList = generateRandom.limit(10000000).collect(Collectors.toList());

     // 串行 - 把一千万个随机数,每个随机数 * 2 ,然后求和
     long start = System.currentTimeMillis();
     int sum = numberList.stream()
         .map(number -> number * 2)
         .mapToInt(x -> x)
         .sum();
     long end = System.currentTimeMillis();
     System.out.println("串行耗时:"+(end - start)+"ms,和是:"+sum);

     // 并行 - 把一千万个随机数,每个随机数 * 2 ,然后求和
     start = System.currentTimeMillis();
     sum = numberList.parallelStream()
         .map(number -> number * 2)
         .mapToInt(x -> x)
         .sum();
     end = System.currentTimeMillis();
     System.out.println("并行耗时:"+(end - start)+"ms,和是:"+sum);
 }

结果:

串行耗时:1005ms,和是:481385106
并行耗时:47ms,和是:481385106

流式操作真香啊_编程_02