【小家java】java8新特性之---Stream API 详解 (Map-reduce、Collectors收集器、并行流、groupby多字段分组)(下)

简介: 【小家java】java8新特性之---Stream API 详解 (Map-reduce、Collectors收集器、并行流、groupby多字段分组)(下)

现在抽取一些不太常用,稍微不太好理解的一些拿来讲一下:


toMap: 若要线程安全的Map,用**toConcurrentMap、groupingByConcurrent**

如果生成一个Map,我们需要调用toMap方法。由于Map中有Key和Value这两个值,故该方法与toSet、toList等的处理方式是不一样的。toMap最少应接受两个参数,一个用来生成key,另外一个用来生成value。toMap方法有三种变形:


注:使用Collectors.toMap方法时的两个问题:

1、当key重复时,会抛出异常:java.lang.IllegalStateException: Duplicate key **

2、当value为null时,会抛出异常:java.lang.NullPointerException


  1. toMap(Function keyMapper,Function valueMapper) keyMapper: 该Funtion用来生成Key valueMapper:该Funtion用来生成value


  public static void main(String[] args) {
        //使用toMap两个参数的(最常用的) 但遇上相同key和null的value都会抛出异常
        //List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        //Map<Integer, Integer> map = list.stream().collect(Collectors.toMap(k -> k, v -> v));
        //System.out.println(map); //{1=1, 2=2, 3=3, 4=4, 5=5}
        //里面放重复的key
        //List<Integer> list = Arrays.asList(1, 2, 3, 4, 1);
        //list.stream().collect(Collectors.toMap(k -> k, v -> v)); //java.lang.IllegalStateException: Duplicate key 1
        //对于里面有重复key的情况,采用三个参数的toMap进行改良
        // java8处理思路:即 两个key相同时 只能有一个key存在,那对应的value如何处理?  value交由我们自己处理
        //Map<Integer, Integer> map = list.stream().collect(Collectors.toMap(k -> k, v -> v, (oldV, newV) -> oldV + newV + 10));
        //System.out.println(map); //{1=12, 2=2, 3=3, 4=4}
        //里面放null值
        //List<Integer> list = Arrays.asList(1, 2, 3, 4, null);
        //list.stream().collect(Collectors.toMap(x -> x, y -> y)); //java.lang.NullPointerException
        //最后 四个参数的toMap 提供了mergeFunction和mapSupplier 调用者可以自定义希望返回什么类型的Map
        List<Integer> list = Arrays.asList(1, 2, 1, 4);
        HashMap<Integer, Integer> map = list.stream().collect(Collectors.toMap(
                k -> k,
                v -> v,
                (oldV, newV) -> oldV + newV + 10,
                HashMap::new)
        );
        System.out.println(map); //{1=12, 2=2, 4=4}
    }


我们常常遇到要把List转成Map的现象。并且要求保证List的顺序,那么此时我们必须使用LinedHashMap,这点特别重要,处理方式如下:


Map<Integer, NormalPeriodResponse> tmpNormalPeriodMap = tmpNormalPeriods.stream()
                .collect(toMap(t -> t.getId(), Function.identity(), (k1, k2) -> k1, LinkedHashMap::new));


public static void main(String[] args) {
        //这个summarizing 算是一个比较整合的搜集
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        IntSummaryStatistics summary = list.stream().collect(Collectors.summarizingInt(x -> x));
        System.out.println(summary.getCount()); //5
        System.out.println(summary.getAverage()); //3.0
        System.out.println(summary.getSum()); //15
    }
public static void main(String[] args) {
        //连接流中的字符串  可以指定连接符、首位符等
        List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
        String str = list.stream().collect(Collectors.joining(",", "==>", "<=="));
        System.out.println(str); //==>aa,bb,cc,dd<==
    }

当使用maxBy、minBy统计最值时,结果会封装在Optional中。有时候明明我们知道不可能为null,那这个时候我们优雅的处理的方式可以采用collectingAndThen函数包裹maxBy、minBy,从而将maxBy、minBy返回的Optional对象进行转换


 public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        Optional<Integer> max = list.stream().collect(Collectors.maxBy(Integer::compare));
        Integer maxAndThen = list.stream().collect(Collectors.collectingAndThen(Collectors.maxBy(Integer::compare), Optional::get));
        System.out.println(max); //Optional[5]
        System.out.println(maxAndThen); //5
    }


备注:groupBy搜集也是用得非常对的,并且可以无限的分组下去。这里需要注意一点groupingByConcurrent的使用方式。他和groupBy的区别就是,它返回的是ConcurrentMap,而普通的就是返回的Map,需要注意区别,这里不做演示了。


多字段分组案例


此处为我后续新增内容,因为很多同学问我多字段怎么groupby,其实非常简单哈。看一下API就能知道怎么处理


    public static <T, K, A, D>
    Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
                                          Collector<? super T, A, D> downstream) {
        return groupingBy(classifier, HashMap::new, downstream);
    }


给个栗子:多字段分组


Map<String, Map<String, List<Article>>> result = articles.stream()
                .collect(Collectors.groupingBy(Article::getCountryCode
                ,Collectors.groupingBy(Article::getProvince)));

给个栗子:分组统计

 //统计每个应用实际支付总额
Map<Long, Long> tradeAmountMap = list.stream().filter(o->o.getStatus()==2)
                .collect(Collectors.groupingBy(OrdersDO::getAppId
                //downstream其实可以做任何搜集的作用
                ,Collectors.summingLong(OrdersDO::getTradeAmount)));


collectingAndThen可用于很多实例,进行持续操作。比如先根据某属性去重,然后再收集等等


分区:partitioningBy

partitioningBy(Predicate predicate)
partitioningBy(Predicate predicate,Collector downstream)

分区是分组的一种特殊情况,它只能分成true、false两组。


下面这个实例:其实就是数据在手上,可以各种玩

public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 5, 5, 6, 9);
        Map<Boolean, List<Integer>> partition = list.stream().collect(Collectors.partitioningBy(x -> x >= 4));
        System.out.println(partition); //{false=[1, 2, 3], true=[4, 5, 5, 5, 6, 9]}
        Map<Boolean, Map<Boolean, List<Integer>>> partition2 = list.stream().collect(Collectors.partitioningBy(x -> x >= 4, Collectors.partitioningBy(x -> x > 6)));
        System.out.println(partition2); //{false={false=[1, 2, 3], true=[]}, true={false=[4, 5, 5, 5, 6], true=[9]}}
        //也可以结合groupBy搞
        Map<Boolean, Map<Integer, List<Integer>>> collect = list.stream().collect(Collectors.partitioningBy(x -> x >= 4, Collectors.groupingBy(x -> x)));
        System.out.println(collect); //{false={1=[1], 2=[2], 3=[3]}, true={4=[4], 5=[5, 5, 5], 6=[6], 9=[9]}}
    }


mapping :跟map操作类似


 String str = Stream.of("a", "b", "c").collect(Collectors.mapping(x -> x.toUpperCase(), Collectors.joining(",")));
        System.out.println(str); //A,B,C

它的源码声明如下:

mapping(Function<? super T, ? extends U> mapper,
                               Collector<? super U, A, R> downstream)

现在我们有了mapping,可以更加优雅的处理如下


List<String> list = Arrays.asList("1", "2", "3");
        Map<Integer, List<Integer>> collect = list.stream().collect(groupingBy(x -> x.hashCode(), mapping(x -> Integer.parseInt(x), toList())));
        System.out.println(collect); //{1=[1], 2=[2], 3=[3]}


生成统计信息(IntSummaryStatistics、DoubleSummaryStatistics等)


另一组非常有用的收集器是用来产生统计信息的收集器。这能够在像int、double和long这样的原始数据类型上起到作用;并且能被用来生成像下面这样的统计信息。


IntSummaryStatistics summaryStatistics = tasks.stream().map(Task::getTitle).collect(summarizingInt(String::length));
System.out.println(summaryStatistics.getAverage()); //32.4
System.out.println(summaryStatistics.getCount()); //5
System.out.println(summaryStatistics.getMax()); //44
System.out.println(summaryStatistics.getMin()); //24
System.out.println(summaryStatistics.getSum()); //162


也有其它的变种形式,像针对其它原生类型的LongSummaryStatistics和DoubleSummaryStatistics。


    public static void main(String[] args) {
        IntSummaryStatistics statistics1 = new IntSummaryStatistics();
        IntSummaryStatistics statistics2 = new IntSummaryStatistics();
        statistics2.combine(statistics1);
    }


你也可以通过使用combine操作来将一个IntSummaryStatistics与另一个组合起来(必须是同一类型哦)。


    public static void main(String[] args) {
        IntSummaryStatistics statistics1 = new IntSummaryStatistics();
        statistics1.accept(10);
        System.out.println(statistics1.getSum()); //10
        System.out.println(statistics1.getCount()); //1
        IntSummaryStatistics statistics2 = new IntSummaryStatistics();
        statistics2.accept(20);
        statistics1.combine(statistics2);
        System.out.println(statistics1.getSum()); //30
        System.out.println(statistics1.getCount()); //2
    }


介绍几个Stream的静态方法


of 这个方法不说了


需要注意的是,不能全是null,否则报错。这个在JDK9做了改善


empty 构造一个空流

public static void main(String[] args) {
        List<Integer> list = Stream.<Integer>empty().collect(toList());
        System.out.println(list); //[]
    }


由此课件,流生成的集合,都是不会为null的


iterate、generate 这两个上面已经介绍了,很好用



concat


顾名思义,就是拼接流。这个在很多场合比较实用。比如要合并提取两个或者更多的List集合的时候,就没必要先合并集合,再处理流了,可以一步到位,并且效率很高。


    public static void main(String[] args) {
        List<Integer> list1 = Arrays.asList(1,2,3);
        List<Integer> list2 = Arrays.asList(4,3,2);
        Stream.concat(list1.stream(),list2.stream()).forEach(System.out::print);
    }


并行流(ParallelStream)


首先简单的介绍下Fork/Join 框架(JDK1.7后提出)

Fork/Join 框架与传统线程池的区别:

采用 “工作窃取”模式 (work-stealing) : 当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中


相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上。在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行。或者当线程任务完成速度快,就会随机抽取其它未完成任务的进程中的最后一个任务进行计算操作。这种方式减少了线程的等待时间,提高了性能


   普通 for(最慢,数据量越大CPU使用率低,速度越慢)


   备注:如果数据量较小,它还是蛮快的,毕竟for循环是偏底层的代码


   ForkJoin框架(比较快) 但任务拆分的代码门槛有点高,使用起来过于复杂


   Java8 并行流(底层使用ForkJoin框架,速度最快 CPU使用率可以达到 100%)


所以,如果是大任务(小任务并行流没有任何效果反而可能还会慢一些),极力推荐使用并行流处理大数量的计算。比如从1加到1000亿的和这种,或者类似的更加耗时的操作(比如多次访问库等等)

Stream的执行原理


Stream的执行原理过于复杂,本文不做过多讨论,请关注后续博文


结束语


Stream 的特性可以归纳为:


不是数据结构


它没有内部存储,它只是用操作管道从 source(数据结构、数组、generator function、IO channel)抓取数据。


它也绝不修改自己所封装的底层数据结构的数据。例如 Stream 的 filter 操作会产生一个不包含被过滤元素的新 Stream,而不是从 source 删除那些元素。


所有 Stream 的操作必须以 lambda 表达式为参数。


不支持索引访问


你可以请求第一个元素,但无法请求第二个,第三个,或最后一个。


惰性化(惰性求值)操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始


并行能力(当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的)


可以是无限的。集合有固定大小,Stream 则不必。limit(n) 和 findFirst() 这类的 short-circuiting 操作可以对无限的 Stream 进行运算并很快完成。


相关文章
|
9天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
9天前
|
分布式计算 Java API
Java 8引入了流处理和函数式编程两大新特性
Java 8引入了流处理和函数式编程两大新特性。流处理提供了一种声明式的数据处理方式,使代码更简洁易读;函数式编程通过Lambda表达式和函数式接口,简化了代码书写,提高了灵活性。此外,Java 8还引入了Optional类、新的日期时间API等,进一步增强了编程能力。这些新特性使开发者能够编写更高效、更清晰的代码。
23 4
|
16天前
|
存储 Java API
Java交换map的key和value值
通过本文介绍的几种方法,可以在Java中实现Map键值对的交换。每种方法都有其优缺点,具体选择哪种方法应根据实际需求和场景决定。对于简单的键值对交换,可以使用简单遍历法或Java 8的Stream API;对于需要处理值不唯一的情况,可以使用集合存储或Guava的Multimap。希望本文对您理解和实现Java中的Map键值对交换有所帮助。
21 1
|
24天前
|
存储 Java API
优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。
【10月更文挑战第19天】本文介绍了如何优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。内容包括Map的初始化、使用Stream API处理Map、利用merge方法、使用ComputeIfAbsent和ComputeIfPresent,以及Map的默认方法。这些技巧不仅提高了代码的可读性和维护性,还提升了开发效率。
47 3
|
24天前
|
存储 Java API
详细解析HashMap、TreeMap、LinkedHashMap等实现类,帮助您更好地理解和应用Java Map。
【10月更文挑战第19天】深入剖析Java Map:不仅是高效存储键值对的数据结构,更是展现设计艺术的典范。本文从基本概念、设计艺术和使用技巧三个方面,详细解析HashMap、TreeMap、LinkedHashMap等实现类,帮助您更好地理解和应用Java Map。
41 3
|
24天前
|
存储 缓存 安全
在Java的Map家族中,HashMap和TreeMap各具特色
【10月更文挑战第19天】在Java的Map家族中,HashMap和TreeMap各具特色。HashMap基于哈希表实现,提供O(1)时间复杂度的高效操作,适合性能要求高的场景;TreeMap基于红黑树,提供O(log n)时间复杂度的有序操作,适合需要排序和范围查询的场景。两者在不同需求下各有优势,选择时需根据具体应用场景权衡。
28 2
|
2月前
|
Go 定位技术 索引
Go 语言Map(集合) | 19
Go 语言Map(集合) | 19
|
2月前
|
存储 前端开发 API
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用
该文章详细介绍了ES6中Set和Map数据结构的特性和使用方法,并探讨了它们在前端开发中的具体应用,包括如何利用这些数据结构来解决常见的编程问题。
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用
|
1月前
|
存储 分布式计算 Java
Stream很好,Map很酷,但答应我别用toMap():Java开发中的高效集合操作
在Java的世界里,Stream API和Map集合无疑是两大强大的工具,它们极大地简化了数据处理和集合操作的复杂度。然而,在享受这些便利的同时,我们也应当警惕一些潜在的陷阱,尤其是当Stream与Map结合使用时。本文将深入探讨Stream与Map的优雅用法,并特别指出在使用toMap()方法时需要注意的问题,旨在帮助大家在工作中更高效、更安全地使用这些技术。
41 0
|
3月前
|
存储 安全 Java
java集合框架复习----(4)Map、List、set
这篇文章是Java集合框架的复习总结,重点介绍了Map集合的特点和HashMap的使用,以及Collections工具类的使用示例,同时回顾了List、Set和Map集合的概念和特点,以及Collection工具类的作用。
java集合框架复习----(4)Map、List、set