关于java:JDK18Stream中常用的API

38次阅读

共计 11030 个字符,预计需要花费 28 分钟才能阅读完成。

1 Stream

Stream 是一组用来解决数组,汇合的 API。

1.1 个性

  1. 不是数据结构,没有外部存储。
  2. 不反对索引拜访。
  3. 提早计算
  4. 反对并行
  5. 很容易生成数据或汇合
  6. 反对过滤,查找,转换,汇总,聚合等操作。

1.2 运行机制

Stream 分为源 source,两头操作,终止操作。

流的源能够是一个数组,汇合,生成器办法,I/ O 通道等等。

一个流能够有零个或多个两头操作,每一个两头操作都会返回一个新的流,供下一个操作应用,一个流只会有一个终止操作。

Stream 只有遇到终止操作,它的源才会开始执行遍历操作。

1.3 Stream 的创立

  1. 通过数组,Stream.of()
  2. 通过汇合
  3. 通过 Stream.generate 办法来创立
  4. 通过 Stram.iterate 办法
  5. 其余 API

2 Stream 罕用的 API

2.1 两头操作

2.1.1 filter 过滤

该操作会承受一个谓词(一个返回 boolean 的函数)作为参数,并返回一个包含所有合乎谓词的元素的流。说白了就是给一个条件,filter 会依据这个条件截取流中得数据。

public static void testFilter(){List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        // 截取所有能被 2 整除得数据
        List<Integer> collect = integers.stream().filter(i -> i % 2 == 0).collect(Collectors.toList());
        System.out.println("collect =" + collect);
    }

后果:
collect = [1, 2, 3, 4]

2.1.2 distinct 去重

该操作会返回一个元素各异(依据流所生成元素的 hashCode 和 equals 办法实现)的流。

public static void main(String[] args) {List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
        List<Integer> collect = numbers.stream().distinct().collect(Collectors.toList());
        System.out.println("collect =" + collect);
}

2.1.3 sorted 排序

对流中得数据进行排序,能够以天然序或着用 Comparator 接口定义的排序规定来排序一个流。Comparator 能应用 lambada 表达式来初始化,还可能逆序一个曾经排序的流。

    public static void main(String[] args) {List<Integer> integers = Arrays.asList(5, 8, 2, 6, 41, 11);
        // 排序默认为程序  程序 = [2, 5, 6, 8, 11, 41]
        List<Integer> sorted = integers.stream().sorted().collect(Collectors.toList());
        System.out.println("程序 =" + sorted);
        // 逆序    逆序 = [41, 11, 8, 6, 5, 2]
        List<Integer> reverseOrder = integers.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
        System.out.println("逆序 =" + reverseOrder);
        // 也能够接管一个 lambda
        List<Integer> ages = integers.stream().sorted(Comparator.comparing(User::getAge)).collect(Collectors.toList());
    }

2.1.4 limit 截取

该办法会返回一个不超过给定长度的流。

public static void testLimit(){List<Integer> integers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
        // 截取流中得前三个元素  collect = [1, 2, 1]
        List<Integer> collect = integers.stream().limit(3).collect(Collectors.toList());
        System.out.println("collect =" + collect);
    }

2.1.5 skip 舍弃

该办法会返回一个扔掉了后面 n 个元素的流。如果流中元素有余 n 个,则返回一个空流。

public static void testSkip() {List<Integer> integers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
        // 丢掉流中得前三个元素 collect = \[3, 3, 2, 4\]
        List<Integer> collect = integers.stream().skip(3).collect(Collectors.toList());
        System.out.println("collect =" + collect);
    }

2.1.6 map 演绎

该办法会承受一个函数作为参数,这个函数会被利用到每个元素上,并将其映射成一个新的元素。就是依据指定函数获取流中得每个元素得数据并重新组合成一个新的元素。

public static void main(String[] args) {
        // 本人建好得一个获取对象 list 得办法
        List<Dish> dishList = Dish.getDishList();
        // 获取每一道菜得名称  并放到一个 list 中
        List<String> collect = dishList.stream().map(Dish::getName).collect(Collectors.toList());
        //collect = [pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon]
        System.out.println("collect =" + collect);
    }

2.1.7 flatMap 扁平化

该办法 key 能够让你把一个流中的每个值都换成另一个流,而后把所有的流都链接起来成为一个流。

给 定 单 词 列 表 [“Hello”,”World”],你想要返回列表 [“H”,”e”,”l”, “o”,”W”,”r”,”d”], 你可能会认为这很容易,通过 map 你能够把每个单词映射成一张字符表,而后调用 distinct 来过滤反复的字符,然而这个办法的问题在于,传递给 map 办法的 Lambda 为每个单词返回了一个 String[](String 列表)。因而,map 返回的流实际上是 Stream<String[]> 类型的。而你真正想要的是用 Stream 来示意一个字符流。

正确写法应该是通过 flatMap 对其扁平化并作出对应解决。

public static void main(String[] args) {String[] words = {"Hello", "World"};
        List<String> collect = Stream.of(words).    // 数组转换流
                map(w -> w.split("")).             // 去掉“”并获取到两个 String[]
                flatMap(Arrays::stream).           // 办法调用将两个 String[] 扁平化为一个 stream
                distinct().                       // 去重    
                collect(Collectors.toList());
        //collect = [H, e, l, o, W, r, d]
        System.out.println("collect =" + collect);
    }
}

2.1.8 peek

peek 的设计初衷就是在流的每个元素复原运行之前,插入执行一个动作。

public static void main(String[] args) {List<Integer> numbers = Arrays.asList(2, 3, 4, 5);
        List<Integer> result =
                numbers.stream()
                        .peek(x -> System.out.println("from stream:" + x))
                        .map(x -> x + 17)
                        .peek(x -> System.out.println("after map:" + x))
                        .filter(x -> x % 2 == 0)
                        .peek(x -> System.out.println("after filter:" + x))
                        .limit(3)
                        .peek(x -> System.out.println("after limit:" + x))
                        .collect(Collectors.toList());
        
    }

后果:

from stream: 2
after map: 19
from stream: 3
after map: 20
after filter: 20
after limit: 20
from stream: 4
after map: 21
from stream: 5
after map: 22
after filter: 22
after limit: 22

2.1.9 collect 收集

从下面得代码曾经能够看进去,collect 是将最终 stream 中得数据收集起来,最终生成一个 list,set,或者 map。

public static void main(String[] args) {List<Dish> dishList = Dish.getDishList();
        //list
        List<Dish> collect = dishList.stream().limit(2).collect(Collectors.toList());
        //set
        Set<Dish> collect1 = dishList.stream().limit(2).collect(Collectors.toSet());
        //map
        Map<String, Dish.Type> collect2 = dishList.stream().limit(2).collect(Collectors.toMap(Dish::getName, Dish::getType));
}

这外面生成 map 得 toMap 办法有三个重载,传入得参数都不同,这里应用得是传入两个 Function 类型得参数。当然,Collectors 的性能还不止这些,上面的收集器中会有其余的详解。

2.2 终止操作

  • 循环 forEach
  • 计算 min、max、count、average
  • 匹配 anyMatch、allMatch、noneMatch、findFirst、findAny
  • 汇聚 reduce
  • 收集器 collect

2.3 查找和匹配

另一个常见的数据处理套路是看看数据集中的某些元素是否匹配一个给定的属性。Stream API 通过 allMatch,anyMatch,noneMatch,findFirst 和 findAny 办法提供了这样的工具。

查找和匹配都是终端操作。

2.3.1 anyMatch

anyMatch 办法能够答复“流中是否有一个元素能匹配到给定的谓词”。会返回一个 boolean 值。

public class AnyMatch {public static void main(String[] args) {List<Dish> dish = Dish.getDish();
        boolean b = dish.stream().anyMatch(Dish::isVegetarian);
        System.out.println(b);
    }
}

2.3.2 allMatch

allMatch 办法和 anyMatch 相似,校验流中是否都能匹配到给定的谓词。

class AllMatch{public static void main(String[] args) {List<Dish> dish = Dish.getDish();
        // 是否所有菜的热量都小于 1000
        boolean b = dish.stream().allMatch(d -> d.getCalories() < 1000);
        System.out.println(b);
    }
}

2.3.3 noneMatch

noneMatch 办法能够确保流中没有任何元素与给定的谓词匹配。

class NoneMatch{public static void main(String[] args) {List<Dish> dish = Dish.getDish();
        // 没有任何菜的热量大于等于 1000
        boolean b = dish.stream().allMatch(d -> d.getCalories() >= 1000);
        System.out.println(b);
    }
}

anyMatch,noneMatch,allMatch 这三个操作都用到了所谓的短路。

2.3.4 findAny

findAny 办法将返回以后流中的任意元素。

class FindAny{public static void main(String[] args) {List<Dish> dish = Dish.getDish();
        Optional<Dish> any = dish.stream().filter(Dish::isVegetarian).findAny();
        System.out.println("any =" + any);
    }
}

2.3.5 findFirst

findFirst 办法能找到你想要的第一个元素。

class FindFirst{public static void main(String[] args) {List<Dish> dish = Dish.getDish();
            Optional<Dish> any = dish.stream().filter(Dish::isVegetarian).findFirst();
            System.out.println("any =" + any);
        }
    }

2.4 归约 reduce

此类查问须要将流中所有元素重复联合起来,失去一个值,比方一个 Integer。这样的查问能够被归类为归约操作(将流归约成一个值)。用函数式编程语言的术语来说,这称为折叠(fold),因为你能够将这个操 作看成把一张长长的纸(你的流)重复折叠成一个小方块,而这就是折叠操作的后果。

2.4.1 元素求和

public static void main(String[] args) {List<Integer> integers = Arrays.asList(1, 2, 3, 6, 8);
        // 求 list 中的和,以 0 为基数
        Integer reduce = integers.stream().reduce(0, (a, b) -> a + b);
        //Integer 的静态方法
        int sum = integers.stream().reduce(0, Integer::sum);
        System.out.println("reduce =" + reduce);
    }

2.4.2 最大值和最小值

public static void main(String[] args) {List<Integer> integers = Arrays.asList(1, 2, 3, 6, 8);
        Optional<Integer> min = integers.stream().reduce(Integer::min);
        System.out.println("min =" + min);
        Optional<Integer> max = integers.stream().reduce(Integer::max);
        System.out.println("max =" + max);
    }

2.5 收集器 Collectors

2.5.1 查找流中的最大值和最小值 minBy maxBy

public static void main(String[] args) {List<Dish> dish = Dish.getDish();
        // 创立一个 Comparator 来进行比拟  比拟菜的卡路里
        Comparator<Dish> dishComparator = Comparator.comparingInt(Dish::getCalories);
        //maxBy 选出最大值
        Optional<Dish> collect = dish.stream().collect(Collectors.maxBy(dishComparator));
        System.out.println("collect =" + collect);
        // 选出最小值
        Optional<Dish> collect1 = dish.stream().collect(Collectors.minBy(dishComparator));
        System.out.println("collect1 =" + collect1);
    }

2.5.2 汇总 summingInt

Collectors.summingInt。它可承受一个把对象映射为求和所需 int 的函数,并返回一个收集器。

 public static void main(String[] args) {List<Dish> dish = Dish.getDish();
        // 计算总和
        int collect = dish.stream().collect(Collectors.summingInt(Dish::getCalories));
        System.out.println("collect =" + collect);
    }

2.5.2 汇总 summingInt

Collectors.summingInt。它可承受一个把对象映射为求和所需 int 的函数,并返回一个收集器。

 public static void main(String[] args) {List<Dish> dish = Dish.getDish();
        // 计算总和
        int collect = dish.stream().collect(Collectors.summingInt(Dish::getCalories));
        System.out.println("collect =" + collect);
    }

2.5.4 连贯字符串 joining

public static void main(String[] args) {List<Dish> dish = Dish.getDish();
        String collect = dish.stream().map(Dish::getName).collect(Collectors.joining());
        System.out.println("collect =" + collect);
    }

joining 工厂办法有一个重载版本能够承受元素之间的分界符,这样你就能够失去一个逗号分隔的菜肴名称列表。

String collect = dish.stream().map(Dish::getName).collect(Collectors.joining(","));

2.5.5 失去流中的总数 counting

long howManyDishes = dish.stream().collect(Collectors.counting());

2.6 分组

2.6.1 分组 groupingBy

public static void main(String[] args) {List<Dish> dish = Dish.getDish();
        //groupingBy 承受一个 function 作为参数
        Map<Dish.Type, List<Dish>> collect = dish.stream().collect(Collectors.groupingBy(Dish::getType));
        System.out.println("collect =" + collect);
    }

如果想用以分类的条件可能比简略的属性拜访器要简单。例如,你可能想把热量不到 400 卡路里的菜划分为“低热量”(diet),热量 400 到 700 卡路里的菜划为“一般”(normal),高于 700 卡路里的划为“高热量”(fat)。因为 Dish 类的作者没有把这个操作写成一个办法,你无奈应用办法援用,但你能够把这个逻辑写成 Lambda 表达式。

public static void main(String[] args) {List<Dish> dishList = Dish.getDish();
        Map<String, List<Dish>> collect = dishList.stream().collect(Collectors.groupingBy(dish->{if (dish.getCalories() <= 400) {return "DIET";} else if (dish.getCalories() <= 700) {return "NORMAL";} else {return "FAT";}
        }));
        System.out.println("collect =" + collect);
    }

2.6.2 多级分组

要实现多级分组,咱们能够应用一个由双参数版本的 Collectors.groupingBy 工厂办法创立的收集器,它除了一般的分类函数之外,还能够承受 collector 类型的第二个参数。那么要进行二级分组的话,咱们能够把一个内层 groupingBy 传递给外层 groupingBy,并定义一个为流中我的项目分类的二级标准。

public static void main(String[] args) {List<Dish> dish = Dish.getDish();
        Map<Dish.Type, Map<String, List<Dish>>> collect = dish.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.groupingBy(d -> {if (d.getCalories() <= 400) {return "DIET";} else if (d.getCalories() <= 700) {return "NORMAL";} else {return "FAT";}
        })));
        System.out.println("collect =" + collect);
    }

2.6.3 按子组收集数据

在上一面,咱们看到能够把第二个 groupingBy 收集器传递给外层收集器来实现多级分组。但进一步说,传递给第一个 groupingBy 的第二个收集器能够是任何类型,而不肯定是另一个 groupingBy。

例如,要数一数菜单中每类菜有多少个,能够传递 counting 收集器作为 groupingBy 收集器的第二个参数。

Map<Dish.Type, Long> typesCount = dish.stream().collect(groupingBy(Dish::getType, counting()));

一般的单参数 groupingBy(f)(其中 f 是分类函数)实际上是 groupingBy(f,toList()) 的简便写法。

把收集器的后果转换为另一种类型:

Collectors.collectingAndThen 工厂办法

3 并行流

并行流就是一个把内容分成多个数据块,并用不同的线程别离解决每个数据块的流。这样一来,你就能够主动把给定操作的工作负荷调配给多核处理器的所有内核,让它们都忙起来。

3.1 将程序流转为并行流

你能够把流转换成并行流,从而让后面的函数归约过程(也就是求和)并行运行——对程序流调用 parallel 办法:

public static long parallelSum(long n) {return Stream.iterate(1L, i -> i + 1)
                .limit(n)
                .parallel()
                .reduce(0L, Long::sum);
    }

Stream 在外部分成了几块。因而能够对不同的块独立并行进行演绎操作,最初,同一个演绎操作会将各个子流的局部演绎后果合并起来,失去整个原始流的演绎后果。
相似地,你只须要对并行流调用 sequential 办法就能够把它变成程序流。

配置并行流应用的线程池

看看流的 parallel 办法,你可能会想,并行流用的线程是从哪儿来的?有多少个?怎么自定义这个过程呢?

并行流外部应用了默认的 ForkJoinPool(7.2 节会进一步讲到分支 / 合并框架),它默认的线 程 数 量 就是 你 的 处 理器 数 量,这个 值 是 由 Runtime.getRuntime().available-Processors() 失去的。但 是 你 可 以 通 过 系 统 属 性 java.util.concurrent.ForkJoinPool.common.parallelism 来扭转线程池大小,如下所示:System.setProperty(“java.util.concurrent.ForkJoinPool.common.parallelism”,”12″); 这是一个全局设置,因而它将影响代码中所有的并行流。反过来说,目前还无奈专为某个并行流指定这个值。一般而言,让 ForkJoinPool 的大小等于处理器数量是个不错的默认值,除非你有很好的理由,否则咱们强烈建议你不要批改它。

3.2 高效应用并行流

咱们宣称并行求和办法应该比程序和迭代办法性能好。然而在软件工程上,靠猜相对不是什么好方法!特地是在优化性能时,你应该始终遵循三个黄金规定:测量,测量,再测量。

  • 如果有疑难,测量。把程序流转成并行流轻而易举,但却不肯定是坏事。咱们在本节中曾经指出,并行流不总是比程序流快。此外,并行流有时候会和你的直觉不统一,所以在思考抉择程序流还是并行流时,第一个也是最重要的倡议就是用适当的基准来查看其性能。
  • 注意装箱。主动装箱和拆箱操作会大大降低性能。Java 8 中有原始类型流(IntStream、LongStream、DoubleStream)来防止这种操作,凡是有可能都应该用这些流。
  • 有些操作自身在并行流上的性能就比程序流差。特地是 limit 和 findFirst 等依赖于元素程序的操作,它们在并行流上执行的代价十分大。例如,findAny 会比 findFirst 性能好,因为它不肯定要按程序来执行。你总是能够调用 unordered 办法来把有序流变成无序流。那么,如果你须要流中的 n 个元素而不是专门要前 n 个的话,对无序并行流调用 limit 可能会比单个有序流(比方数据源是一个 List)更高效。
  • 还要思考流的操作流水线的总计算成本。设 N 是要解决的元素的总数,Q 是一个元素通过流水线的大抵解决老本,则 N * Q 就是这个对老本的一个粗略的定性预计。Q 值较高就意味着应用并行流时性能好的可能性比拟大。
  • 对于较小的数据量,抉择并行流简直素来都不是一个好的决定。并行处理少数几个元素的益处还抵不上并行化造成的额定开销。
  • 要思考流背地的数据结构是否易于合成。例如,ArrayList 的拆分效率比 LinkedList 高得多,因为前者用不着遍历就能够均匀拆分,而后者则必须遍历。另外,用 range 工厂办法创立的原始类型流也能够疾速合成。
  • 流本身的特点,以及流水线中的两头操作批改流的形式,都可能会扭转合成过程的性能。例如,一个 SIZED 流能够分成大小相等的两局部,这样每个局部都能够比拟高效地并行处理,但筛选操作可能抛弃的元素个数却无奈预测,导致流自身的大小未知。
  • 还要思考终端操作中合并步骤的代价是大是小(例如 Collector 中的 combiner 办法)。如果这一步代价很大,那么组合每个子流产生的局部后果所付出的代价就可能会超出通过并行流失去的性能晋升。

正文完
 0