Java8 stream 中利用 groupingBy 进行多字段分组求和案例

作者:琦彦 时间:2023-03-22 00:54:43 

Java8的groupingBy实现集合的分组,类似Mysql的group by分组功能,注意得到的是一个map

对集合按照单个属性分组、分组计数、排序


List<String> items =
   Arrays.asList("apple", "apple", "banana",
       "apple", "orange", "banana", "papaya");

// 分组
Map<String, List<String>> result1 = items.stream().collect(
   Collectors.groupingBy(
       Function.identity()
   )
);
//{papaya=[papaya], orange=[orange], banana=[banana, banana], apple=[apple, apple, apple]}
System.out.println(result1);

// 分组计数
Map<String, Long> result2 = items.stream().collect(
   Collectors.groupingBy(
       Function.identity(), Collectors.counting()
   )
);
// {papaya=1, orange=1, banana=2, apple=3}
System.out.println(result2);
Map<String, Long> finalMap = new LinkedHashMap<>();

//分组, 计数和排序
result2.entrySet().stream()
   .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
   .forEachOrdered(e -> finalMap.put(e.getKey(), e.getValue()));
// {apple=3, banana=2, papaya=1, orange=1}
System.out.println(finalMap);

集合按照多个属性分组

1.多个属性拼接出一个组合属性


public static void main(String[] args) {
 User user1 = new User("zhangsan", "beijing", 10);
 User user2 = new User("zhangsan", "beijing", 20);
 User user3 = new User("lisi", "shanghai", 30);
 List<User> list = new ArrayList<User>();
 list.add(user1);
 list.add(user2);
 list.add(user3);
 Map<String, List<User>> collect = list.stream().collect(Collectors.groupingBy(e -> fetchGroupKey(e)));
 //{zhangsan#beijing=[User{age=10, name='zhangsan', address='beijing'}, User{age=20, name='zhangsan', address='beijing'}],
 // lisi#shanghai=[User{age=30, name='lisi', address='shanghai'}]}
 System.out.println(collect);
}

private static String fetchGroupKey(User user){
 return user.getName() +"#"+ user.getAddress();
}

2.嵌套调用groupBy


User user1 = new User("zhangsan", "beijing", 10);
User user2 = new User("zhangsan", "beijing", 20);
User user3 = new User("lisi", "shanghai", 30);
List<User> list = new ArrayList<User>();
list.add(user1);
list.add(user2);
list.add(user3);
Map<String, Map<String, List<User>>> collect
   = list.stream().collect(
       Collectors.groupingBy(
           User::getAddress, Collectors.groupingBy(User::getName)
       )
);
System.out.println(collect);

3. 使用Arrays.asList

我有一个与Web访问记录相关的域对象列表。这些域对象可以扩展到数千个。

我没有资源或需求将它们以原始格式存储在数据库中,因此我希望预先计算聚合并将聚合的数据放在数据库中。

我需要聚合在5分钟窗口中传输的总字节数,如下面的sql查询


select
round(request_timestamp, '5') as window, --round timestamp to the nearest 5 minute
cdn,
isp,
http_result_code,
transaction_time,
sum(bytes_transferred)
from web_records
group by
 round(request_timestamp, '5'),
 cdn,
 isp,
 http_result_code,
 transaction_time

在java 8中,我当前的第一次尝试是这样的,我知道这个解决方案类似于Group by multiple field names in java 8


Map<Date, Map<String, Map<String, Map<String, Map<String, Integer>>>>>>> aggregatedData =
webRecords
 .stream()
 .collect(Collectors.groupingBy(WebRecord::getFiveMinuteWindow,
       Collectors.groupingBy(WebRecord::getCdn,
        Collectors.groupingBy(WebRecord::getIsp,
         Collectors.groupingBy(WebRecord::getResultCode,
           Collectors.groupingBy(WebRecord::getTxnTime,
            Collectors.reducing(0,
                      WebRecord::getReqBytes(),
                      Integer::sum)))))));

这是可行的,但它是丑陋的,所有这些嵌套的地图是一个噩梦!要将地图“展平”或“展开”成行,我必须这样做


for (Date window : aggregatedData.keySet()) {
for (String cdn : aggregatedData.get(window).keySet()) {
 for (String isp : aggregatedData.get(window).get(cdn).keySet()) {
  for (String resultCode : aggregatedData.get(window).get(cdn).get(isp).keySet()) {
   for (String txnTime : aggregatedData.get(window).get(cdn).get(isp).get(resultCode).keySet()) {

Integer bytesTransferred = aggregatedData.get(window).get(cdn).get(distId).get(isp).get(resultCode).get(txnTime);
     AggregatedRow row = new AggregatedRow(window, cdn, distId...

如你所见,这是相当混乱和难以维持。

有谁知道更好的方法吗?任何帮助都将不胜感激。

我想知道是否有更好的方法来展开嵌套的映射,或者是否有一个库允许您对集合进行分组。

最佳答案

您应该为地图创建自定义密钥。最简单的方法是使用Arrays.asList:


Function<WebRecord, List<Object>> keyExtractor = wr ->
 Arrays.<Object>asList(wr.getFiveMinuteWindow(), wr.getCdn(), wr.getIsp(),
      wr.getResultCode(), wr.getTxnTime());

Map<List<Object>, Integer> aggregatedData = webRecords.stream().collect(
  Collectors.groupingBy(keyExtractor, Collectors.summingInt(WebRecord::getReqBytes)));

在这种情况下,键是按固定顺序列出的5个元素。不是很面向对象,但很简单。或者,您可以定义自己的表示自定义键的类型,并创建适当的hashCode/equals实现。

补充知识:java8 新特性 Stream流 分组 排序 过滤 多条件去重 (最小、最大、平均、求和)

什么是 Stream?

Stream 是用函数式编程方式在集合类上进行复杂操作的工具,其集成了Java 8中的众多新特性之一的聚合操作,开发者可以更容易地使用Lambda表达式,并且更方便地实现对集合的查找、遍历、过滤以及常见计算等。话不多说,直接上代码。


List<User> list = new ArrayList<User>();
list = Arrays.asList(
   new User("小强", 11, "男"),
   new User("小玲", 15, "女"),
   new User("小虎", 23, "男"),
   new User("小雨", 26, "女"),
   new User("小飞", 19, "男"),
   new User("小玲", 15, "女")
);
//分组
Map<String, List<User>> listMap = list.stream().collect(Collectors.groupingBy(User::getSex));
for(String key:listMap.keySet()){
 System.out.print(key+"组:");
 listMap.get(key).forEach(user -> System.out.print(user.getName()));
 System.out.println();
}
//排序
list.stream().sorted(Comparator.comparing(user-> user.getAge()))
   .forEach(user -> System.out.println(user.getName()));
//过滤
list.stream().filter(user -> user.getSex().equals("男")).collect(Collectors.toList())
   .forEach(user -> System.out.println(user.getName()));
//多条件去重
list.stream().collect(Collectors.collectingAndThen(
   Collectors.toCollection(() -> new TreeSet<>(
       Comparator.comparing(user -> user.getAge() + ";" + user.getName()))), ArrayList::new))
   .forEach(user -> System.out.println(user.getName()));
//最小值
Integer min = list.stream().mapToInt(User::getAge).min().getAsInt();
//最大值
Integer max = list.stream().mapToInt(User::getAge).max().getAsInt();
//平均值
Double average = list.stream().mapToInt(User::getAge).average().getAsDouble();
//和
Integer sum = list.stream().mapToInt(User::getAge).sum();
System.out.println("最小值:"+min+", 最大值"+max+", 平均值:"+average+", 和:"+sum);
//分组求和
Map<String, IntSummaryStatistics> collect = list.stream().collect(Collectors.groupingBy(User::getSex, Collectors.summarizingInt(User::getAge)));
IntSummaryStatistics statistics1 = collect.get("男");
IntSummaryStatistics statistics2 = collect.get("女");
System.out.println(statistics1.getSum());
System.out.println(statistics1.getAverage());
System.out.println(statistics1.getMax());
System.out.println(statistics1.getMin());
System.out.println(statistics1.getCount());
System.out.println(statistics2.getSum());
System.out.println(statistics2.getAverage());
System.out.println(statistics2.getMax());
System.out.println(statistics2.getMin());
System.out.println(statistics2.getCount());
//提取list中两个属性值,转为map
Map<String, String> userMap = list.stream().collect(Collectors.toMap(User::getName, User::getSex));
System.out.println(JsonUtil.toJson(userMap))
//取出所有名字
List<String> names = list.stream().map(User::getName).collect(Collectors.toList());
System.out.println(JsonUtil.toJson(names))

来源:https://blog.csdn.net/fly910905/article/details/104005444

标签:Java8,stream,groupingBy,多字段,求和
0
投稿

猜你喜欢

  • Java结构型设计模式之桥接模式详细讲解

    2023-08-26 16:25:56
  • c#分页读取GB文本文件实例

    2021-09-13 10:18:39
  • Java Date时间类型的操作实现

    2023-11-25 06:44:31
  • 深入C# 内存管理以及优化的方法详解

    2021-10-24 14:24:03
  • 详解如何为SpringBoot项目中的自定义配置添加IDE支持

    2021-10-07 05:51:01
  • SpringCloud使用Zookeeper作为注册中心

    2023-11-21 20:28:20
  • C#中WebBroeser控件用法实例教程

    2021-09-14 18:11:05
  • 一篇文章带你了解Java Stream流

    2022-11-05 10:26:18
  • Android BottomSheet效果的两种实现方式

    2022-10-14 02:26:23
  • C#构造函数在基类和父类中的执行顺序

    2021-10-24 06:58:03
  • 使用C++的string实现高精度加法运算的实例代码

    2023-06-10 19:43:47
  • C#中字符串的一般性和特殊性

    2023-03-23 19:06:07
  • Android之在linux终端执行shell脚本直接打印当前运行app的日志的实现方法

    2021-06-12 23:41:08
  • Java 二分法检索算法代码实现详解

    2022-01-05 19:13:24
  • C#实现IDisposable接口释放非托管资源

    2022-12-01 06:59:53
  • MyBatis如何调用存储过程与存储函数

    2023-06-14 16:10:51
  • java开发分布式服务框架Dubbo调用过程

    2022-04-13 14:29:55
  • 详解Spring整合Ehcache管理缓存

    2022-02-10 00:50:17
  • Spring Security认证提供程序示例详解

    2022-11-07 18:06:40
  • Android显示GIF图片实例代码

    2021-10-14 16:47:03
  • asp之家 软件编程 m.aspxhome.com