【问题标题】:How to group into map of arrays?如何分组为数组映射?
【发布时间】:2018-12-09 17:49:44
【问题描述】:

流上的groupingBy 操作能否生成一个映射,其中值是数组,而不是列表或其他一些集合类型?

例如:我有一个班级Thing。事物有所有者,所以Thing 有一个getOwnerId 方法。在事物流中,我想按所有者 ID 对事物进行分组,以便具有相同所有者 ID 的事物最终一起出现在一个数组中。换句话说,我想要一个像下面这样的地图,其中键是所有者 ID,值是属于该所有者的事物的数组。

    Map<String, Thing[]> mapOfArrays;

就我而言,由于我需要将映射值传递给需要数组的库方法,因此收集到Map&lt;String, Thing[]&gt; 是最方便的。

将整个流收集到一个数组中很容易(甚至不需要显式收集器):

    Thing[] arrayOfThings = Stream.of(new Thing("owner1"), new Thing("owner2"), new Thing("owner1"))
            .toArray(Thing[]::new);

[属于owner1,属于owner2,属于owner1]

按所有者 ID 搜索也很容易。例如,分组到列表中:

    Map<String, List<Thing>> mapOfLists = Stream.of(new Thing("owner1"), new Thing("owner2"), new Thing("owner1"))
            .collect(Collectors.groupingBy(Thing::getOwnerId));

{owner1=[属于 owner1, 属于 owner1], owner2=[属于 owner2]}

只有这个例子给了我一个列表的地图。有 2-arg 和 3-arg groupingBy 方法可以为我提供其他集合类型(如集合)的映射。我想,如果我可以将收集到数组中的收集器(类似于上面第一个 sn-p 中的收集到数组中)传递给两个参数 Collectors.groupingBy​(Function&lt;? super T,? extends K&gt;, Collector&lt;? super T,A,D&gt;),我会被设置。但是,Collectors 类中的预定义收集器似乎都没有对数组做任何事情。我错过了一个不太复杂的方法吗?

为了一个完整的例子,这是我在上面的 sn-ps 中使用的类:

public class Thing {

    private String ownerId;

    public Thing(String ownerId) {
        this.ownerId = ownerId;
    }

    public String getOwnerId() {
        return ownerId;
    }

    @Override
    public String toString() {
        return "Belongs to " + ownerId;
    }

}

【问题讨论】:

标签: java arrays java-stream grouping


【解决方案1】:

使用来自this answer by Thomas Pliakas的收集器:

    Map<String, Thing[]> mapOfArrays = Stream.of(new Thing("owner1"), new Thing("owner2"), new Thing("owner1"))
            .collect(Collectors.groupingBy(Thing::getOwnerId,
                    Collectors.collectingAndThen(Collectors.toList(),
                            tl -> tl.toArray(new Thing[0]))));

这个想法是首先收集到一个列表中(这是一个显而易见的想法,因为数组具有恒定大小),然后在返回 grouping by 收集器之前转换为数组。 collectingAndThen 可以通过其所谓的终结者来做到这一点。

打印检查结果:

    mapOfArrays.forEach((k, v) -> System.out.println(k + '=' + Arrays.toString(v)));
owner1=[Belongs to owner1, Belongs to owner1]
owner2=[Belongs to owner2]

编辑:感谢 Aomine 提供链接:使用 new Thing[0] 作为 toArray 的参数受到 Arrays of Wisdom of the Ancients 的启发。似乎最终在 Intel CPU 上使用new Thing[0] 比使用new Thing[tl.size()] 更快。我很惊讶。

【讨论】:

  • 次要,但对于打印,您可以执行mapOfArrays.forEach((k, v) -&gt; ...)。可以避免拨打entrySet()。另外,您可能最好使用tl.toArray(new Thing[0]) 而不是tl.toArray(new Thing[tl.size()])(有关更多详细信息,请参阅Arrays of Wisdom of the Ancients)否则很好的解决方案1+。
  • @Aomine 一篇非常令人惊讶的文章,至少对我而言。我已编辑并更改为tl.toArray(new Thing[0])。再次感谢。
  • 这不是英特尔特有的东西。相反,临时零大小数组实例和反射数组创建都不会对性能产生任何影响,并且如果创建站点和覆盖代码太远,HotSpot JVM 无法消除新数组的零填充,例如调用者提供的数组。请注意,这些知识也进入了 JDK,请参阅 JDK11’s toArray​(IntFunction&lt;T[]&gt;) 方法...
【解决方案2】:

你可以先分组,然后使用后续的 toMap:

Map<String, Thing[]> result = source.stream()
                .collect(groupingBy(Thing::getOwnerId))
                .entrySet()
                .stream()
                .collect(toMap(Map.Entry::getKey,
                        e -> e.getValue().toArray(new Thing[0])));

【讨论】:

  • 谢谢。这可能比我自己的答案更容易理解。不过,我也喜欢我自己的想法,即在一个终端操作中完成整个操作。
  • @OleV.V.是的,你的看起来更紧凑,所以我可以看到你来自哪里。无论如何都有选择。
  • 坦率地说,我不知道是接受你的回答还是我自己的回答。我相信各有各的优点。我也接受你的建议,因为我们现在有更多的选择可以考虑和选择。
  • @OleV.V.嘿,Ole,我完全同意,两者当然都有各自的优势,如前所述,我喜欢你的回答的紧凑性。至于接受哪个答案,请选择您认为对“读者”最有利的答案。谢谢。
【解决方案3】:

可能很明显,但您可以通过以下方式完成:

Stream.of(new Thing("owner1"), new Thing("owner2"), new Thing("owner1"))
            .collect(Collectors.toMap(
                    Thing::getOwnerId,
                    x -> new Thing[]{x},
                    (left, right) -> {
                        Thing[] newA = new Thing[left.length + right.length];
                        System.arraycopy(left, 0, newA, 0, left.length);
                        System.arraycopy(right, 0, newA, left.length, right.length);
                        return newA;
                    }
            ))

【讨论】:

  • 我想到了这个想法,但是看起来很浪费资源,所以我没有检查它是否可行。感谢您证明它是。有不同的提议真是太好了。对于我的情况(通常包含 1 到 20 个元素的流),效率可能无关紧要,因此可以选择。
  • 如果有人想稍微压缩一下,那么我想他们可以将合并功能更改为 --> (left, right) -&gt; Stream.concat(Arrays.stream(left), Arrays.stream(right)).toArray(Thing[]::new)
  • @Aomine 顺便说一句,我不是故意这样做的,在我看来,两个 System::arraycopy 会更快...
  • @Eugene 是的,我似乎已经想通了。无论如何我想我会发表评论,以展示不同的方式......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-17
  • 2018-03-14
  • 2016-05-21
相关资源
最近更新 更多