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

简介: Java8 stream 中利用 groupingBy 进行多字段分组求和

 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);

image.gif

集合按照多个属性分组

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();
}

image.gif

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);

image.gif

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

image.gif

在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)))))));

image.gif

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

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...

image.gif

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

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

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

最佳答案

您应该为地图创建自定义密钥。最简单的方法是使用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)));

image.gif

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

参考链接:

    目录
    相关文章
    |
    存储 SQL Java
    Java8 stream 中利用 groupingBy 进行多字段分组求和
    Java8 stream 中利用 groupingBy 进行多字段分组求和
    2569 0
    |
    8月前
    |
    Java
    Java8 Stream Collectors groupingBy使用
    Java8 Stream Collectors groupingBy使用
    |
    安全 Java
    Java 8 - 收集器Collectors_分组groupingBy
    Java 8 - 收集器Collectors_分组groupingBy
    320 0
    |
    Java
    java8中使用groupingBy分组返回有序的Map
    java8中使用groupingBy分组返回有序的Map
    482 0
    java8集合类聚合GroupingBy的妙用
    对于简单的一些list聚合操作,可以用java8的一些聚合工具,一行代码即可实现。
    674 0
    |
    9天前
    |
    Java
    Java—多线程实现生产消费者
    本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
    Java—多线程实现生产消费者
    |
    11天前
    |
    安全 Java Kotlin
    Java多线程——synchronized、volatile 保障可见性
    Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
    |
    11天前
    |
    消息中间件 缓存 安全
    Java多线程是什么
    Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
    |
    11天前
    |
    安全 Java 编译器
    深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
    `synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
    35 3
    |
    11天前
    |
    存储 安全 Java
    Java多线程编程秘籍:各种方案一网打尽,不要错过!
    Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
    93 2