JAVA与Streams的聚合,这是一个很好的方法吗?

时间:2017-05-17 18:28:29

标签: java performance java-8 java-stream collectors

我正在玩一些Java流,我想出了一个问题的解决方案,我想与你分享,看看我的方法是否正确。

我已经从https://catalog.data.gov/dataset/consumer-complaint-database下载了一个数据集,该数据集有700,000多个客户投诉记录。 我使用的信息如下:

公司名称 产品名称

我的目标是通过以下方式获得结果:

在数据集中出现次数更多的10家公司

数据集中出现次数更多的10个产品

得到像

这样的东西
Map<String, Map<String,Integer>>

其中,主地图的密钥是公司名称,辅助地图中的密钥是产品名称,其值是产品在该公司中投诉的次数。

所以我所做的解决方案如下:

@Test
public void joinGroupingsTest() throws URISyntaxException, IOException {
    String path = CsvReaderTest.class.getResource("/complains.csv").toURI().toString();
    complains = CsvReader.readFileStreamComplain(path.substring(path.indexOf('/')+1));

    Map<String, List<Complain>> byCompany = complains.parallelStream()
            .collect(Collectors.groupingBy(Complain::getCompany))
            .entrySet().stream()
            .sorted((f1, f2) -> Long.compare(f2.getValue().size(), f1.getValue().size()))
            .limit(10)
            .collect(Collectors.toMap(Entry::getKey, Entry::getValue));


    Map<String, List<Complain>> byProduct = complains.parallelStream()
            .collect(Collectors.groupingBy(Complain::getProduct))
            .entrySet().stream()
            .sorted((f1, f2) -> Long.compare(f2.getValue().size(), f1.getValue().size()))
            .limit(10)
            .collect(Collectors.toMap(Entry::getKey, Entry::getValue));

    Map<String, List<Complain>> map = complains.parallelStream()
            .filter((x) -> byCompany.get(x.getCompany()) != null
                && byProduct.get(x.getProduct()) != null)
            .collect(Collectors.groupingBy(Complain::getCompany));

    Map<String, Map<String, Long>> map2 = map.entrySet().parallelStream()
            .collect(Collectors.toMap(
                    e -> e.getKey(),
                    e -> e.getValue().stream()
                            .collect(Collectors.groupingBy(Complain::getProduct, Collectors.counting()))
            ));


   System.out.println(map2);


}

正如您所看到的,我有几个步骤来实现这个目标:

1)我让10家公司的出现次数和投诉(记录)相关联

2)我得到10个产品出现次数更多且抱怨(记录)相关

3)我得到一张公司名称的地图,作为之前计算的前10家公司中的关键,以及同样位于前10名产品中的产品的投诉

4)我进行了转换以获得我想要的地图。

除了在两个不同的线程中分叉和分离步骤1和2之外,还有其他考虑因素,我可能需要提高性能,甚至以更好的方式使用流。

谢谢!

2 个答案:

答案 0 :(得分:3)

在前两个操作中,您是List s的集合组,只是按其大小排序。这显然是浪费资源,因为您可以在分组时简单地计算组元素,然后按计数排序。此外,由于前两个操作是相同的,除了分组功能之外,还可以通过创建任务的方法来删除代码重复。

其他两个流操作可以在一个中完成,方法是在收集组时对组执行collect操作。

public void joinGroupingsTest() throws URISyntaxException, IOException {
    String path = CsvReaderTest.class.getResource("/complains.csv").toURI().toString();
    complains = CsvReader.readFileStreamComplain(path.substring(path.indexOf('/')+1));

    Set<String> byCompany = getTopTen(complains, Complain::getCompany);
    Set<String> byProduct = getTopTen(complains, Complain::getProduct);

    Map<String, Map<String, Long>> map = complains.stream()
            .filter(x -> byCompany.contains(x.getCompany())
                      && byProduct.contains(x.getProduct()))
            .collect(Collectors.groupingBy(Complain::getCompany,
                Collectors.groupingBy(Complain::getProduct, Collectors.counting())));
   System.out.println(map);
}
static <T,V> Set<V> getTopTen(Collection<T> source, Function<T,V> criteria) {
    return source.stream()
            .collect(Collectors.groupingBy(criteria, Collectors.counting()))
            .entrySet().stream()
            .sorted(Map.Entry.comparingByValue())
            .limit(10)
            .map(Map.Entry::getKey)
            .collect(Collectors.toSet());
}

请注意,两个标准的交集可能小于十个元素,甚至可能是空的。你可能会重新考虑这个条件。

此外,您应该始终重新检查数据量是否真的足够大,以便从并行处理中受益。另请注意,getTopTen操作包含两个流操作。将第一个切换为并行不会改变第二个的性质。

答案 1 :(得分:0)

如果您不必处理性能,使用Java流是很好的方法。 Java流或并行流相对较慢,如果流中的操作有任何异常,可能会使调试变得更糟。流的好处是您必须编写几行代码来解决复杂的聚合问题或数据结构更改。这是一个链接,您可以在其中了解java流与传统方法相比的缓慢程度。

https://blog.codefx.org/java/stream-performance/