Java 8按收集器分组指南

1.介绍

使用各种示例来了解groupingBy收集器的工作方式。

2.groupingBy Collectors

Java 8 Stream API能够以声明的方式处理数据集合。

静态工厂方法Collectors.groupingBy()和Collectors.groupingByConcurrent()提供了类似于SQL语言中“ GROUP BY”子句的功能。 使用它们将对象按某些属性分组,并将结果存储在Map实例中。

groupingBy的重载方法是

  • 首先,以分类函数作为方法参数:
static <T,K> Collector<T,?,Map<K,List<T>>> 
  groupingBy(Function<? super T,? extends K> classifier)
  • 其次,使用分类函数和第二个收集器作为方法参数:
static <T,K,A,D> Collector<T,?,Map<K,D>>
  groupingBy(Function<? super T,? extends K> classifier, 
    Collector<? super T,A,D> downstream)
  • 最后,通过分类函数,提供者方法(提供包含最终结果的Map实现)和第二个收集器作为方法参数
static <T,K,D,A,M extends Map<K,D>> Collector<T,?,M>
  groupingBy(Function<? super T,? extends K> classifier, 
    Supplier<M> mapFactory, Collector<? super T,A,D> downstream)

2.1示例代码准备

为了演示groupingBy()的用法,让定义一个BlogPost类(将使用BlogPost对象流):

@Data
@AllArgsConstructor
@NoArgsConstructor
public class BlogPost {
    String title;
    String author;
    BlogPostType type;
    int likes;
}
public enum BlogPostType {
    /**
     *
     */
    NEWS,
    REVIEW,
    GUIDE
}

定义清单

 static List<BlogPost> posts = new ArrayList<>();
   static {
       posts.add(new BlogPost("map使用用法", "张三", BlogPostType.NEWS,1));
       posts.add(new BlogPost("List使用用法", "李四", BlogPostType.GUIDE,1));
       posts.add(new BlogPost("Set使用用法", "张三", BlogPostType.REVIEW,1));
       posts.add(new BlogPost("Stream使用用法", "赵六", BlogPostType.NEWS,1));
       posts.add(new BlogPost("Thread使用用法", "田七", BlogPostType.GUIDE,1));
   }

还定义一个Tuple类,该类将用于通过组合其类型和作者属性来对帖子进行分组:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Tuple {
    BlogPostType type;
    String author;
}

2.2 通过单列进行简单分组

从最简单的groupingBy方法开始,该方法仅将分类函数作为其参数。 分类函数应用于流的每个元素。 使用函数返回的值作为从groupingBy收集器获取的映射的键。

要将博客文章按其类型分组在博客文章列表中,请执行以下操作:

  @Test
   public void test1(){
       Map<BlogPostType, List<BlogPost>> postsPerType = posts.stream()
               .collect(groupingBy(BlogPost::getType));
       System.out.println(postsPerType);
   }

输出如下:

{REVIEW=[BlogPost(title=Set使用用法, author=张三, type=REVIEW, likes=1)], NEWS=[BlogPost(title=map使用用法, author=张三, type=NEWS, likes=1), BlogPost(title=Stream使用用法, author=赵六, type=NEWS, likes=1)], GUIDE=[BlogPost(title=List使用用法, author=李四, type=GUIDE, likes=1), BlogPost(title=Thread使用用法, author=田七, type=GUIDE, likes=1)]}

2.3通过复杂映射键类型进行分组

分类函数不限于仅返回标量或String值。 只要确保实现必要的equals和hashcode方法,结果映射的键就可以是任何对象。

要将列表中的博客文章按类型和作者(在Tuple实例中组合)进行分组:

   @Test
   public  void test2(){
       Map<Tuple, List<BlogPost>> postsPerTypeAndAuthor = posts.stream()
               .collect(groupingBy(post -> new Tuple(post.getType(), post.getAuthor())));
       System.out.println(postsPerTypeAndAuthor);

   }

输出

{  Tuple(type=NEWS, author=赵六)=[BlogPost(title=Stream使用用法, author=赵六, type=NEWS, likes=1)],
  Tuple(type=NEWS, author=张三)=[BlogPost(title=map使用用法, author=张三, type=NEWS, likes=1)], 
  Tuple(type=GUIDE, author=李四)=[BlogPost(title=List使用用法, author=李四, type=GUIDE, likes=1)], 
  Tuple(type=GUIDE, author=田七)=[BlogPost(title=Thread使用用法, author=田七, type=GUIDE, likes=1)], 
  Tuple(type=REVIEW, author=张三)=[BlogPost(title=Set使用用法, author=张三, type=REVIEW, likes=1)]}

2.4修改返回的Map值类型

groupingBy的第二次重载使用了一个附加的第二收集器(下游收集器),该收集器应用于第一收集器的结果。

指定分类函数但不指定下游收集器时,将在后台使用toList()收集器。

使用toSet()收集器作为下游收集器,并获取一组博客文章(而不是List):

  @Test
   public  void test3(){
       Map<BlogPostType, Set<BlogPost>> postsPerType = posts.stream()
               .collect(groupingBy(BlogPost::getType, toSet()));
       System.out.println(postsPerType);
   }

输出


{REVIEW=[BlogPost(title=Set使用用法, author=张三, type=REVIEW, likes=1)], 
NEWS=[BlogPost(title=map使用用法, author=张三, type=NEWS, likes=1),
BlogPost(title=Stream使用用法, author=赵六, type=NEWS, likes=1)], 
GUIDE=[BlogPost(title=Thread使用用法, author=田七, type=GUIDE, likes=1), 
BlogPost(title=List使用用法, author=李四, type=GUIDE, likes=1)]}

2.5按多个字段分组

下游收集器的另一种应用是对第一组的结果进行二级分组。

要按作者然后按类型对BlogPost列表进行分组:

   @Test
   public  void test4(){
       Map<String, Map<BlogPostType, List<BlogPost>>> map = posts.stream()
               .collect(groupingBy(BlogPost::getAuthor, groupingBy(BlogPost::getType)));
       System.out.println(map);
   }

输出

{李四={GUIDE=[BlogPost(title=List使用用法, author=李四, type=GUIDE, likes=1)]}, 
张三={REVIEW=[BlogPost(title=Set使用用法, author=张三, type=REVIEW, likes=1)], NEWS=[BlogPost(title=map使用用法, author=张三, type=NEWS, likes=1)]}, 
赵六={NEWS=[BlogPost(title=Stream使用用法, author=赵六, type=NEWS, likes=1)]}, 
田七={GUIDE=[BlogPost(title=Thread使用用法, author=田七, type=GUIDE, likes=1)]}}

2.6从分组结果中获取平均值

通过使用下游收集器,可以将聚合函数应用到分类函数的结果中。

例如,要查找每种博客文章类型的平均点赞次数:

   @Test
   public void test5(){
       Map<BlogPostType, Double> averageLikesPerType = posts.stream()
               .collect(groupingBy(BlogPost::getType, averagingInt(BlogPost::getLikes)));
       System.out.println(averageLikesPerType);
   }

输出

{NEWS=1.0, GUIDE=1.0, REVIEW=1.0}

2.7从分组结果中获取总和

要计算每种类型的喜欢总数:

   @Test
   public void test6(){
       Map<BlogPostType, Integer> likesPerType = posts.stream()
               .collect(groupingBy(BlogPost::getType, summingInt(BlogPost::getLikes)));
       System.out.println(likesPerType);
   }

输出

{NEWS=2, GUIDE=2, REVIEW=1}

2.8从分组结果中获取最大值或最小值

可以执行的另一种汇总方式是获得具有最多“赞”次数的博客帖子:

   @Test
   public void test7(){
       Map<BlogPostType, Optional<BlogPost>> maxLikesPerPostType = posts.stream()
               .collect(groupingBy(BlogPost::getType,
                       maxBy(comparingInt(BlogPost::getLikes))));
       System.out.println(maxLikesPerPostType);

   }

输出

{
GUIDE=Optional[BlogPost(title=List使用用法, author=李四, type=GUIDE, likes=1)], 
NEWS=Optional[BlogPost(title=map使用用法, author=张三, type=NEWS, likes=1)], 
REVIEW=Optional[BlogPost(title=Set使用用法, author=张三, type=REVIEW, likes=1)]
}

同样,我们可以应用minBy下游收集器来获得具有最少点赞次数的博客文章。

请注意,maxBy和minBy收集器考虑了应用它们的集合可能为空的可能性。 这就是为什么映射中的值类型为Optional 的原因。

2.9获取分组结果属性的总和

Collectors API提供了一个汇总收集器,可以在需要同时计算数值属性的计数,总和,最小值,最大值和平均值的情况下使用。

每种不同类型的博客文章的likes属性计算一个总和:

   @Test
   public void test8(){
       Map<BlogPostType, IntSummaryStatistics> likeStatisticsPerType = posts.stream()
               .collect(groupingBy(BlogPost::getType,
                       summarizingInt(BlogPost::getLikes)));

       System.out.println(likeStatisticsPerType);
   }

输出

{
 REVIEW=IntSummaryStatistics{count=1, sum=1, min=1, average=1.000000, max=1}, 
 NEWS=IntSummaryStatistics{count=2, sum=2, min=1, average=1.000000, max=1}, 
 GUIDE=IntSummaryStatistics{count=2, sum=2, min=1, average=1.000000, max=1}
 }

2.10将分组结果映射到其他类型

通过将映射下游收集器应用于分类函数的结果,可以实现更复杂的聚合。

连接每种博客文章类型的文章标题:

    @Test
   public void test9(){
       Map<BlogPostType, String> postsPerType = posts.stream()
               .collect(groupingBy(BlogPost::getType,
                       mapping(BlogPost::getTitle, joining(", ", "Post titles: [", "]"))));
       System.out.println(postsPerType);
   }

输出

{ 
REVIEW=Post titles: [Set使用用法], 
NEWS=Post titles: [map使用用法, Stream使用用法], 
GUIDE=Post titles: [List使用用法, Thread使用用法]
}

2.11修改返回Map类型

使用groupingBy收集器时,无法对返回的Map的类型进行假设。 如果要具体确定要从分组依据中获取哪种类型的地图,则可以使用groupingBy方法的第三个变体,该方法允许通过传递Map类型来更改地图的类型。

接下来将EnumMap提供程序函数传递给groupingBy方法来检索EnumMap:

 @Test
   public void test10(){
       EnumMap<BlogPostType, List<BlogPost>> postsPerType = posts.stream()
               .collect(groupingBy(BlogPost::getType,
                       () -> new EnumMap<>(BlogPostType.class), toList()));
       System.out.println(postsPerType);
   }

输出:

{ 
NEWS=[BlogPost(title=map使用用法, author=张三, type=NEWS, likes=1), BlogPost(title=Stream使用用法, author=赵六, type=NEWS, likes=1)], 
REVIEW=[BlogPost(title=Set使用用法, author=张三, type=REVIEW, likes=1)], 
GUIDE=[BlogPost(title=List使用用法, author=李四, type=GUIDE, likes=1), BlogPost(title=Thread使用用法, author=田七, type=GUIDE, likes=1)]
}

2.12分组返回并发收集器

与groupingBy类似的是groupingByConcurrent收集器,该收集器利用了多核体系结构。 该收集器具有三个重载方法,它们采用与groupingBy收集器的各个重载方法完全相同的参数。 但是,groupingByConcurrent收集器的返回类型必须是ConcurrentHashMap类的实例或其子类。

要同时执行分组操作,流必须是并行的:

   @Test
   public  void test11(){
       ConcurrentMap<BlogPostType, List<BlogPost>> postsPerType = posts.parallelStream()
               .collect(groupingByConcurrent(BlogPost::getType));
       System.out.println(postsPerType);
   }

输出

{
GUIDE=[BlogPost(title=Thread使用用法, author=田七, type=GUIDE, likes=1), BlogPost(title=List使用用法, author=李四, type=GUIDE, likes=1)], 
NEWS=[BlogPost(title=Stream使用用法, author=赵六, type=NEWS, likes=1), BlogPost(title=map使用用法, author=张三, type=NEWS, likes=1)], REVIEW=[BlogPost(title=Set使用用法, author=张三, type=REVIEW, likes=1)]
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
内容简介   本书从Hadoop的缘起开始,由浅入深,结合理论和实践,全方位地介绍Hadoop这一高性能处理海量数据集的理想工具。全书共16章,3个附录,涉及的主题包括:Haddoop简介;MapReduce简介;Hadoop分布式文件系统;Hadoop的I/O、MapReduce应用程序开发;MapReduce的工作机制;MapReduce的类型和格式;MapReduce的特性;如何构建Hadoop集群,如何管理Hadoop;Pig简介;Hbase简介;Hive简介;ZooKeeper简介;开源工具Sqoop,最后还提供了丰富的案例分析。   本书是Hadoop权威参考,程序员可从中探索如何分析海量数据集,管理员可以从中了解如何安装与运行Hadoop集群。 目录 第1章 初识Hadoop  数据!数据!  数据存储与分析  与其他系统相比  关系型数据库管理系统  网格计算  志愿计算  1.3.4 Hadoop 发展简史  Apache Hadoop和Hadoop生态圈 第2章 关于MapReduce  一个气象数据集  数据的格式  使用Unix工具进行数据分析  使用Hadoop分析数据  map阶段和reduce阶段  横向扩展  合并函数  运行一个分布式的MapReduce作业  Hadoop的Streaming  Ruby版本  Python版本  Hadoop Pipes  编译运行 第3章 Hadoop分布式文件系统  HDFS的设计  HDFS的概念  数据块  namenode和datanode  命令行接口  基本文件系统操作  Hadoop文件系统  接口  Java接口  从Hadoop URL中读取数据  通过FileSystem API读取数据  写入数据  目录  查询文件系统  删除数据  数据流  文件读取剖析  文件写入剖析  一致模型  通过 distcp并行拷贝  保持 HDFS 集群的均衡  Hadoop的归档文件  使用Hadoop归档文件  不足 第4章 Hadoop I/O  数据完整性  HDFS的数据完整性  LocalFileSystem  ChecksumFileSystem  压缩  codec  压缩和输入切分  在MapReduce中使用压缩  序列化  Writable接口  Writable类  实现定制的Writable类型  序列化框架  Avro  依据文件的数据结构  写入SequenceFile  MapFile 第5章 MapReduce应用开发  配置API  合并多个源文件  可变的扩展  配置开发环境  配置管理  辅助类GenericOptionsParser,Tool和ToolRunner  编写单元测试  mapper  reducer  本地运行测试数据  在本地作业运行器上运行作业  测试驱动程序  在集群上运行  打包  启动作业  MapReduce的Web界面  获取结果  作业调试  使用远程调试器  作业调优  分析任务  MapReduce的工作流  将问题分解成MapReduce作业  运行独立的作业 第6章 MapReduce的工作机制  剖析MapReduce作业运行机制  作业的提交  作业的初始化  任务的分配  任务的执行  进度和状态的更新  作业的完成  失败  任务失败  tasktracker失败  jobtracker失败  作业的调度  Fair Scheduler  Capacity Scheduler  shuffle和排序  map端  reduce端  配置的调优  任务的执行  推测式执行  重用JVM  跳过坏记录  任务执行环境 第7章 MapReduce的类型与格式  MapReduce的类型  默认的MapReduce作业  输入格式  输入分片与记录  文本输入  二进制输入  多种输入  数据库输入(和输出)  输出格式  文本输出  二进制输出  多个输出  延迟输出  数据库输出 第8章 MapReduce的特性  计数器  内置计数器  用户定义的Java计数器  用户定义的Streaming计数器  排序  准备  部分排序  总排序  二次排序  联接  map端联接  reduce端联接  边数据分布  利用JobConf来配置作业  分布式缓存  MapReduce库类 第9章 构建Hadoop集群  集群规范  网络拓扑  集群的构建和安装  安装Java  创建Hadoop用户  安装Hadoop  测试安装  SSH配置  Hadoop配置  配置管理  环境设置  Hadoop守护进程的关键属性  Hadoop守护进程的地址和端口  Hadoop的其他属性  创建用户帐号  安全性  Kerberos和Hadoop  委托令牌  其他安全性改进  利用基准测试程序测试Hadoop集群  Hadoop基准测试程序  用户的作业  云上的Hadoop  Amazon EC2上的Hadoop 第10章 管理Hadoop  HDFS  永久性数据结构  安全模式  日志审计  工具  监控  日志  度量  Java管理扩展(JMX)  维护  日常管理过程  委任节点和解除节点  升级 第11章 Pig简介  安装与运行Pig  执行类型  运行Pig程序  Grunt  Pig Latin编辑器  示例  生成示例  与数据库比较  PigLatin  结构  语句  表达式  1.4.4 类型  模式  函数  用户自定义函数  过滤UDF  计算UDF  加载UDF  数据处理操作  加载和存储数据  过滤数据  分组与连接数据  对数据进行排序  组合和分割数据  Pig实战  并行处理  参数代换 第12章 Hive  1.1 安装Hive  1.1.1 Hive外壳环境  1.2 示例  1.3 运行Hive  1.3.1 配置Hive  1.3.2 Hive服务  1.3.3 Metastore  1.4 和传统数据库进行比较  1.4.1 读时模式(Schema on Read)vs.写时模式(Schema onWrite)  1.4.2 更新、事务和索引  1.5 HiveQL  1.5.1 数据类型  1.5.2 操作和函数  1.6 表  1.6.1 托管表(Managed Tables)和外部表(External Tables)  1.6.2 分区(Partitions)和桶(Buckets)  1.6.3 存储格式  1.6.4 导入数据  1.6.5 表的修改  1.6.6 表的丢弃  1.7 查询数据  1.7.1 排序(Sorting)和聚集(Aggregating)  1.7.2 MapReduce脚本  1.7.3 连接  1.7.4 子查询  1.7.5 视图(view)  1.8 用户定义函数(User-Defined Functions)  1.8.1 编写UDF  1.8.2 编写UDAF 第13章 HBase  2.1 HBasics  2.1.1 背景  2.2 概念  2.2.1 数据模型的“旋风之旅”  2.2.2 实现  2.3 安装  2.3.1 测试驱动  2.4 客户机  2.4.1 Java  2.4.2 Avro,REST,以及Thrift  2.5 示例  2.5.1 模式  2.5.2 加载数据  2.5.3 Web查询  2.6 HBase和RDBMS的比较  2.6.1 成功的服务  2.6.2 HBase  2.6.3 实例:HBase在Streamy.com的使用  2.7 Praxis  2.7.1 版本  2.7.2 HDFS  2.7.3 用户接口(UI)  2.7.4 度量(metrics)  2.7.5 模式设计  2.7.6 计数器  2.7.7 批量加载(bulkloading) 第14章 ZooKeeper  安装和运行ZooKeeper  示例  ZooKeeper中的组成员关系  创建组  加入组  列出组成员  ZooKeeper服务  数据模型  操作  实现  一致性  会话  状态  使用ZooKeeper来构建应用  配置服务  具有可恢复性的ZooKeeper应用  锁服务  生产环境中的ZooKeeper  可恢复性和性能  配置 第15章 开源工具Sqoop  获取Sqoop  一个导入的例子  生成代码  其他序列化系统  深入了解数据库导入  导入控制  导入和一致性  直接模式导入  使用导入的数据  导入的数据与Hive  导入大对象  执行导出  深入了解导出  导出与事务  导出和SequenceFile 第16章 实例分析  Hadoop 在Last.fm的应用  Last.fm:社会音乐史上的革命  Hadoop a Last.fm  用Hadoop产生图表  Track Statistics程序  总结  Hadoop和Hive在Facebook的应用  概要介绍  Hadoop a Facebook  假想的使用情况案例  Hive  问题与未来工作计划  Nutch 搜索引擎  背景介绍  数据结构  Nutch系统利用Hadoop进行数据处理的精选实例  总结  Rackspace的日志处理  简史  选择Hadoop  收集和存储  日志的MapReduce模型  关于Cascading  字段、元组和管道  操作  Tap类,Scheme对象和Flow对象  Cascading实战  灵活性  Hadoop和Cascading在ShareThis的应用  总结  在Apache Hadoop上的TB字节数量级排序  使用Pig和Wukong来探索10亿数量级边的 网络图  测量社区  每个人都在和我说话:Twitter回复关系图  degree(度)  对称链接  社区提取 附录A 安装Apache Hadoop  先决条件  安装  配置  本机模式  伪分布模式  全分布模式 附录B Cloudera’s Distribution for Hadoop 附录C 准备NCDC天气数据

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值