Java 8 个流将一个列表 <Map<>> 按相同 <Key, Value> 分组到一个新列表<Map<>>

Java 8 stream grouping a List<Map<>> by the same <Key, Value> to a new List<Map<>>

我有一个List<Map<String,String>> 如:

Map<String, String> m1 = new HashMap<>();
m1.put("date", "2020.1.5");
m1.put("B", "10");

Map<String, String> m2 = new HashMap<>();
m2.put("date", "2020.1.5");
m2.put("A", "20");

Map<String, String> m3 = new HashMap<>();
m3.put("date", "2020.1.6");
m3.put("A", "30");

Map<String, String> m4 = new HashMap<>();
m4.put("date", "2020.1.7");
m4.put("C", "30");

List<Map<String, String>> before = new ArrayList<>();
before.add(m1);
before.add(m2);
before.add(m3);
before.add(m4);

我期望的结果是生成一个新的List map,按日期分组,所有在同一日期集合的条目会放在一起,如:

[{"A":"20","B":"10","date":"2020.1.5"},{"A":"30","date":"2020.1.6"},{"C":"30","date":"2020.1.7"}]

我尝试了以下方法,但始终不是我期望的结果。

stream().flatmap().collect(Collectors.groupingBy())

对这个问题的一些补充意见:

我用 for LOOP 解决了这个问题,但是当列表大小约为 50000 时应用程序挂起,所以我寻求一种性能更好的方法来执行此操作。 Java 据我所知,8 流平面图可能是一种方式。 所以关键点不仅在于重新映射,还在于使用最高效的方式来做到这一点。

before
  .stream()
  .collect(Collectors.toMap((m) -> m.get("date"), m -> m, (a,b) -> {
      Map<String, String> res = new HashMap<>();
      res.putAll(a);
      res.putAll(b);
      return res;
  }))
  .values();

这就是您正在寻找的解决方案。

toMap函数接收3个参数:

  • 键映射器,在你的例子中是日期
  • 值映射器,即正在处理的映射本身
  • 合并功能,将 2 个具有相同日期的地图并将所有键放在一起

输出:

[{date=2020.1.5, A=20, B=10}, {date=2020.1.6, A=30}, {date=2020.1.7, C=30}]

您可以按顺序使用一定数量的收集器获得完全相同的结果:

  • Collectors.groupingBy 按日期分组
  • Collectors.reducing 合并 Map<String, String>
  • Collectors.collectingAndThen 将值从 Map<String, Optional<Map<String, String>>> 转换为先前减少到最终输出 List<Map<String, String>>.
  • 的结果
List<Map<String, String>> list = before.stream()
    .collect(Collectors.collectingAndThen(
        Collectors.groupingBy(
            m -> m.get("date"),
            Collectors.reducing((l, r) -> {
                l.putAll(r);
                return l; })
        ),
        o -> o.values().stream()
                       .flatMap(Optional::stream)
                       .collect(Collectors.toList())));

list 包含您要查找的内容:

[{date=2020.1.5, A=20, B=10}, {date=2020.1.6, A=30}, {date=2020.1.7, C=30}]

重要:这个解决方案有两个缺点:

  • 它看起来很笨拙,独立观众可能看不清楚
  • 它变异(修改)了 List<Map<String, String>> before 中包含的原始地图。

可以这样操作:

List<Map<String, String>> remapped = before.stream()
    .collect(Collectors.groupingBy(m -> m.get("date")))
    .values().stream()
    .map(e -> e.stream()
               .flatMap(m -> m.entrySet().stream())
               .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (x1, x2) -> x1)))
    .collect(Collectors.toList());

remapped.forEach(System.out::println);

输出:

{date=2020.1.5, A=20, B=10}
{date=2020.1.6, A=30}
{date=2020.1.7, C=30}

您可以使用 groupingByCollector.of

List<Map<String, String>> list = new ArrayList<>(before.stream()
        .collect(Collectors.groupingBy(
                k -> k.get("date"),
                Collector.of( HashMap<String,String>::new,
                        (m,e)-> m.putAll(e),
                        (map1,map2)->{ map1.putAll(map2); return map1;}
                ))).values());

这里先用Collectors.groupingBy按日期分组。然后使用 Collector.of 定义自定义收集器,将 List<Map<String, String>> 收集到 Map<String, String>。使用地图值创建列表后。

并使用 Java 9

中的 Collectors.flatMapping
List<Map<String, String>> list = new ArrayList<>(before.stream()
        .collect(Collectors.groupingBy(
                k -> k.get("date"),
                Collectors.flatMapping(m -> m.entrySet().stream(), 
                    Collectors.toMap(k -> k.getKey(), v -> v.getValue(), (a,b) -> a))))
               .values());