stream流

作者: 扎Zn了老Fe | 来源:发表于2019-05-26 21:16 被阅读0次

    1. 简述

    流是有关算法和计算的,允许你以声明性方式处理数据集合,可以看成遍历数据集的的高级迭代器。

    此外,和迭代器不同,流还可以并行处理。数据可以分成多段,其中每一个都在不同的线程中执行,最后将结果一起输出。

    当我们使用一个流的时候,通常包括三个基本步骤:

    获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道,如下图所示。

    图 1. 流管道 (Stream Pipeline) 的构成

    流有三大特点:

    1. 流不储存元素;
    2. 流不会修改其数据源;
    3. 流执行具有延迟特性;

    2. 流的创建

    2.1 从数组或集合

    • Arrays.stream(T array) or Stream.of()
    • Collection.stream()
    • Collection.parallelStream()
    @Test
        public void testArrayStream() {
            //1.通过Arrays.stream
            //1.1基本类型
            Integer[] arr = new Integer[]{1,2,3,4,5,6,7};
    
            //通过Arrays.stream
            Stream stream1 = Arrays.stream(arr);
    
            stream1.forEach(System.out::print);
    
            //2.通过Stream.of
            Stream<Integer> stream2 = Stream.of(1,2,3,4,5,6,7);
    
            stream2.forEach(System.out::print);
        }
    
        @Test
        public void testCollectionStream(){
            List<Integer> strs = Arrays.asList(1,2,3,4,5,6,7);
            //创建普通流
            Stream<Integer> stream  = strs.stream();
            //创建并行流
            Stream<Integer> stream1 = strs.parallelStream();
        }
    

    2.2 创建无限流

    @Test
        public void testUnlimitStream() {
    
            Random seed = new Random();
            Supplier<Integer> random = seed::nextInt;
            Stream.generate(random).limit(10).forEach(System.out::println);
    
            //Another way
            IntStream.generate(() -> (int) (System.nanoTime() % 100)).limit(10).forEach(System.out::println);
        }
    

    2.3 创建规律流

        @Test
        public void testUnlimitStream1(){
            Stream.iterate(0,x -> x+1).limit(10).forEach(System.out::println);
            Stream.iterate(0,x -> x).limit(10).forEach(System.out::println);
        }
    

    3. 流的操作

    流操作分为中间操作和终端操作,中间操作会返回另外一个流,可以继续执行下一个中间操作。
    终端操作是返回结果,终端操作触发流执行中间操作,终端操作,到此流的生命结束。

    3.1 中间操作

    filter
    filter对原始Stream进行过滤,符合条件的原数被留下来生成新的流。

    @Test
        public void testFilter() {
            Integer[] sixNums = {1, 2, 3, 4, 5, 6};
            Integer[] evens = Stream.of(sixNums).filter(n -> n%2 == 0).toArray(Integer[]::new);
        }
    

    sorted
    sorted对原始Stream中的元素进行排序,排序后生成新的流。

      Integer[] sixNums = {1, 2, 4, 3, 5, 6};
            Integer[] orderedNums = Stream.of(sixNums).sorted(Comparator.comparing(x -> x)).toArray(Integer[]::new);
    

    distinct
    去掉重复值

    @Test
        public void testDistinct() {
            Integer[] sixNums = {1, 2, 4, 3, 5, 5, 6};
            Integer[] distinctedNums = Stream.of(sixNums).distinct().toArray(Integer[]::new);
            System.out.println(distinctedNums);
        }
    

    map
    map将现有流转换为新的流。map接收一个函数作为参数,该函数作用于流中的每一个元素,并将其映射成一个新的元素。

    @Test
    public void testMap() {
        String[] arr = new String[]{"yes", "YES", "no", "NO"};
        Arrays.stream(arr).map(x -> x.toLowerCase()).forEach(System.out::println);
    }
    

    flatMap
    将流扁平化,流中的每一个元素都被拆解成一个新的流。使用flatMap需要提前知道原来的流中的元素类型。

    @Test
        public void testFlapMap1() {
            String[] words = {"Hello", "World"};
            Stream.of(words)
                    .map(word -> word.split(""))
                    .flatMap(Arrays::stream).forEach(System.out::println);
        }
    

    limit
    截断流,该方法返回一个不超过给定长度的流。

    3.2 终端操作

    遍历流中的每一个元素。当需要为多核系统优化时,可以 parallelStream().forEach(),只是此时原有元素的次序没法保证,并行的情况下将改变串行时操作的行为。

    @Test
        public void testFlapMap1() {
            String[] words = {"Hello", "World"};
            Stream.of(words)
                    .map(word -> word.split(""))
                    .flatMap(Arrays::stream).forEach(System.out::println);
        }
    

    findFirst, findAny

    两者功能类似,findAny不要求顺序,使用并行流有优势。
    @Test
        public void testFindFirser() {
            String[] arr = new String[]{"yes", "YES", "no", "NO"};
            Arrays.stream(arr).filter(str -> Objects.equals(str, "yes")).findFirst().ifPresent(System.out::println);
        }
    

    allMatch, noneMatch, anyMatch
    Stream提供了三个match方法,allMatch要求流中所有元素满足条件才返回true, anyMatch中只要有一个元素符合就返回true, noneMatch与allMatch相反,所有元素都不符合,返回true

    @Test
        public void testMatch() {
            String[] arr = new String[]{"yes", "YES", "no", "NO"};
            System.out.println(Arrays.stream(arr).noneMatch( str  -> str.length() > 2));
            System.out.println(Arrays.stream(arr).anyMatch( str  -> str.length() > 2));
            System.out.println(Arrays.stream(arr).allMatch( str  -> str.length() > 2));
        }
    

    reduce
    这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相当于

    Integer sum = integers.reduce(0, (a, b) -> a+b); 
    

    Integer sum = integers.reduce(0, Integer::sum);
    
    @Test
        public void testReduce() {
            // 字符串连接,concat = "ABCD"
            String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
            // 求最小值,minValue = -3.0
            double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
            // 求和,sumValue = 10, 有起始值
            int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
            // 求和,sumValue = 10, 无起始值
            sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
            // 过滤,字符串连接,concat = "ace"
            concat = Stream.of("a", "B", "c", "D", "e", "F").filter(x -> x.compareTo("Z") > 0).reduce("", String::concat);
        }
    

    count
    计算流中元素数量。
    collect
    这个在实际项目中使用非常多,通过collect生成结果。可以生成各种形式,Array, List, Set, Map。另外,还能进行分组。

    public class Student {
            private String name;
            private Integer score;
            //-----getters and setters-----
    
            Student(String name, Integer score) {
                this.name = name;
                this.score = score;
            }
    
            public String getName() {
                return name;
            }
    
            public Integer getScore() {
                return score;
            }
    
            public String toString() {
                return "name: " + name
                        + " score: " + score;
            }
        }
    
        Student[] students;
    
        @Before
        public void init(){
            students = new Student[10];
            for (int i = 0; i <= 3; i++){
                Student student = new Student("user", i);
                students[i] = student;
            }
            for (int i = 3; i <= 6; i++){
                Student student = new Student("user" + i, i + 1);
                students[i] = student;
            }
            for (int i = 6; i < 10; i++){
                Student student = new Student("user" + i, i + 2);
                students[i] = student;
            }
    
        }
        @Test
        public void testCollect1(){
            /**
             * 生成List
             */
            List<Student> list = Arrays.stream(students).collect(Collectors.toList());
            list.forEach((x) -> System.out.println(x));
            /**
             * 生成Set
             */
            Set<Student> set = Arrays.stream(students).collect(Collectors.toSet());
            set.forEach((x)-> System.out.println(x));
            /**
             * 生成Map,如果包含相同的key,则需要提供第三个参数,否则报错, 这里用了覆盖旧value
             */
            Map<String,Integer> map = Arrays.stream(students).collect(Collectors.toMap(Student::getName, Student::getScore, (v1, v2) -> v2));
            map.forEach((x, y) -> System.out.println(x + "->" + y));
        }
    
        /**
         * 生成数组
         */
        @Test
        public void testCollect2(){
            Student[] s = Arrays.stream(students).toArray(Student[]::new);
            for (int i=0; i< s.length; i++)
                System.out.println(s[i]);
        }
    

    groupingBy
    groupingBy能够将流按照某个条件进行进行分组和分片,条件为key,分组之后的元素组成新的流,对新的流可以进行中间操作和终端操作,默认生成List。最后生成Map的类型,key就是分组条件,value取决于对分组后的流进行操作后生成的类型。生成Map会有重复key的问题,使用groupingBy将重复key的value值放到一个List, 同样能够解决此问题。

    如果想要按照多个条件进行分组,一种方法是groupingB嵌套使用groupBy,多次进行分组;另一种方法是按照多个分组条件进行拼接成一个key,按照这个拼接后的key进行分组。

    @Test
        public void testGroupBy1(){
            Map<String,List<Student>> map = Arrays.stream(students).collect(Collectors.groupingBy(Student::getName));
            map.forEach((x,y)-> System.out.println(x+"->"+y));
        }
    
        /**
         * 如果只有两类,使用partitioningBy会比groupingBy更有效率
         */
        @Test
        public void testPartitioningBy(){
            Map<Boolean,List<Student>> map = Arrays.stream(students).collect(Collectors.partitioningBy(x -> x.getScore() > 5));
            map.forEach((x, y)-> System.out.println(x+ "->" + y));
        }
    
        /**
         * downstream指定类型
         */
        @Test
        public void testGroupBy2(){
            Map<String,Set<Student>> map = Arrays.stream(students).collect(Collectors.groupingBy(Student::getName, Collectors.toSet()));
            map.forEach((x, y)-> System.out.println(x + "->"+ y));
        }
    
        /**
         * downstream 聚合操作
         */
        @Test
        public void testGroupBy3(){
            /**
             * counting
             */
            Map<String,Long> map1 = Arrays.stream(students).collect(Collectors.groupingBy(Student::getName, Collectors.counting()));
            map1.forEach((x, y)-> System.out.println(x + "->" + y));
            /**
             * summingInt
             */
            Map<String,Integer> map2 = Arrays.stream(students).collect(Collectors.groupingBy(Student::getName, Collectors.summingInt(Student::getScore)));
            map2.forEach((x,y) -> System.out.println(x + "->" + y));
            /**
             * maxBy
             */
            Map<String,Optional<Student>> map3 = Arrays.stream(students).collect(Collectors.groupingBy(Student::getName, Collectors.maxBy(Comparator.comparing(Student::getScore))));
            map3.forEach((x, y)-> System.out.println(x + "->" + y));
            
            /**
             * mapping, 这种也用的比较多,可以用元素的变量作为value, 也可以创建新的对象作为value
             */
            Map<String,Set<Integer>> map4 = Arrays.stream(students).collect(Collectors.groupingBy(Student::getName, Collectors.mapping(Student::getScore, Collectors.toSet())));
            map4.forEach((x, y)-> System.out.println(x + "->" + y));
    
            /**
             * mapping, 使用元素多个变量进行分组
             */
            Map<String, Map<Integer, List<Student>>> map5 = Arrays.stream(students).collect(Collectors.groupingBy(Student::getName, Collectors.groupingBy(Student::getScore)));
            map5.forEach((x, y)-> System.out.println(x + "->" + y));
    
            /**
             * mapping, 使用元素多个变量进行分组, 第二种方法,将多个变量进行拼接, 个人推荐使用这种
             */
            Map<String, List<Student>> map6 = Arrays.stream(students).collect(Collectors.groupingBy(student -> student.getName() + "_" + student.getScore()));
            map6.forEach((x, y)-> System.out.println(x + "->" + y));
        }
    

    总结

    总之,Stream 的特性可以归纳为:

    • 不是数据结构
    • 它也绝不修改自己所封装的底层数据结构的数据。
    • 很容易生成数组或者List
    • 惰性化
      很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要。
    • 并行能力
      当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的。
    • 可以是无限的
      集合有固定大小,Stream 则不必。limit(n) 和 findFirst() 这类的 short-circuiting 操作可以对无限的 Stream 进行运算并很快完成。
      参考
      [1].https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/
      [2]. https://www.cnblogs.com/andywithu/p/7404101.html

    相关文章

      网友评论

          本文标题:stream流

          本文链接:https://www.haomeiwen.com/subject/jcgsjqtx.html