JDK1.8-Stream中经常使用的API(流操做)

1 Stream

Stream是一组用来处理数组,集合的API。java

1.1 特性

  1. 不是数据结构,没有内部存储。
  2. 不支持索引访问。
  3. 延迟计算
  4. 支持并行
  5. 很容易生成数据或集合
  6. 支持过滤,查找,转换,汇总,聚合等操做。

1.2 运行机制

Stream分为源source,中间操做,终止操做。编程

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

一个流能够有零个或多个中间操做,每个中间操做都会返回一个新的流,供下一个操做使用,一个流只会有一个终止操做。bash

Stream只有遇到终止操做,它的源才会开始执行遍历操做。数据结构

1.3 Stream的建立

  1. 经过数组,Stream.of()
  2. 经过集合
  3. 经过Stream.generate方法来建立
  4. 经过Stram.iterate方法
  5. 其余API
import java.util.Arrays;
    import java.util.List;
    import java.util.stream.IntStream;
    import java.util.stream.Stream;
    
    public class CreateStream {
    
    	//经过数组,Stream.of()
        static void gen1(){
            String[] str = {"a","b","c"};
            Stream<String> str1 = Stream.of(str);
        }
    
    	//经过集合
        static void gen2(){
            List<String> strings = Arrays.asList("a", "b", "c");
            Stream<String> stream = strings.stream();
        }
    
    	//经过Stream.generate方法来建立
        static void gen3(){
        	//这是一个无限流,经过这种方法建立在操做的时候最好加上limit进行限制
            Stream<Integer> generate = Stream.generate(() -> 1);
            generate.limit(10).forEach(x -> System.out.println(x));
        }
        
    	//经过Stram.iterate方法
        static void gen4(){
            Stream<Integer> iterate = Stream.iterate(1, x -> x +1);
            iterate.forEach(x -> System.out.println(x));
        }
    
    	//其余API
        static void gen5(){
            String str = "abc";
            IntStream chars = str.chars();
            chars.forEach(x -> System.out.println(x));
        }
    }
复制代码

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 = [2, 4, 6, 8, 10]
复制代码

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);
}
复制代码

结果:ide

collect = [1, 2, 3, 4]
复制代码

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.3 平均数 averagingInt

public static void main(String[] args) {
        List<Dish> dish = Dish.getDish();
        //计算平均数
        Double collect = dish.stream().collect(Collectors.averagingInt(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 方法)。若是这一步代价很大,那么组合每一个子流产生的部分结果所付出的代价就可能会超出经过并行流获得的性能提高。

在这里插入图片描述

3.3 分支/合并框架

分支/合并框架的目的是以递归方式将能够并行的任务拆分红更小的任务,而后将每一个子任务的结果合并起来生成总体结果。它是 ExecutorService 接口的一个实现,它把子任务分配给线程池(称为 ForkJoinPool )中的工做线程。

3.3.1 使用RecursiveTask

要把任务提交到这个池,必须建立 RecursiveTask 的一个子类,其中 R 是并行化任务(以 及全部子任务)产生的结果类型,或者若是任务不返回结果,则是 RecursiveAction 类型(当 然它可能会更新其余非局部机构)。

要定义 RecursiveTask, 只需实现它惟一的抽象方法compute :

protected abstract R compute();
复制代码

这个方法同时定义了将任务拆分红子任务的逻辑,以及没法再拆分或不方便再拆分时,生成单个子任务结果的逻辑。

在这里插入图片描述

3.3.2 使用RecursiveTask求和

public class ForkJoinSumCalculator
        extends java.util.concurrent.RecursiveTask<Long> {
    private final long[] numbers;
    private final int start;
    private final int end;
    public static final long THRESHOLD = 10_000;

    public ForkJoinSumCalculator(long[] numbers) {
        this(numbers, 0, numbers.length);
    }

    private ForkJoinSumCalculator(long[] numbers, int start, int end) {
        this.numbers = numbers;
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        int length = end - start;
        if (length <= THRESHOLD) {
            return computeSequentially();
        }
        //建立一个子任务来为数组得前一半求和
        ForkJoinSumCalculator leftTask =
                new ForkJoinSumCalculator(numbers, start, start + length / 2);
        //利 用 另 一 个ForkJoinPool线程异步执行新建立的子任务
        leftTask.fork();
        //建立一个子任务来为数组得后一半求和
        ForkJoinSumCalculator rightTask =
                new ForkJoinSumCalculator(numbers, start + length / 2, end);
        //同步执行第二个子任务,有可能进一步递归
        Long rightResult = rightTask.compute();
        //读取第一个任务得结构,未完成就等待
        Long leftResult = leftTask.join();
        return leftResult + rightResult;
    }

    private long computeSequentially() {
        long sum = 0;
        for (int i = start; i < end; i++) {
            sum += numbers[i];
        }
        return sum;
    }

    public static long forkJoinSum(long n) {
        long[] numbers = LongStream.rangeClosed(1, n).toArray();
        ForkJoinTask<Long> task = new ForkJoinSumCalculator(numbers);
        return new ForkJoinPool().invoke(task);
    }

    public static void main(String[] args) {
        long l = ForkJoinSumCalculator.forkJoinSum(5);
        System.out.println("l = " + l);
    }

}
复制代码
相关文章
相关标签/搜索