我如何知道Java Stream收集(Collectors.toMap)是否已并行化?

时间:2015-12-05 00:06:34

标签: java parallel-processing java-stream

我有以下代码尝试通过Java Stream API以并行方式从List填充Map:

class NameId {...}

public class TestStream
{
    static public void main(String[] args)
    {
        List<NameId > niList = new ArrayList<>();
        niList.add(new NameId ("Alice", "123456"));
        niList.add(new NameId ("Bob", "223456"));
        niList.add(new NameId ("Carl", "323456"));

        Stream<NameId> niStream = niList.parallelStream();
        Map<String, String> niMap = niStream.collect(Collectors.toMap(NameId::getName, NameId::getId));
    }
}

我如何知道是否使用多个线程填充地图,即并行?我是否需要调用Collectors.toConcurrentMap而不是Collectors.toMap?这是一种合理的方式来并行化地图的人口吗?我怎么知道具体的地图支持新的niMap(例如它是HashMap)?

3 个答案:

答案 0 :(得分:2)

来自Javadoc

  

返回的收集器不是并发的。对于并行流管道,组合器功能通过将键从一个映射合并到另一个映射来操作,这可能是昂贵的操作。如果不需要将结果以遭遇顺序插​​入到Map中,则使用toConcurrentMap(Function,Function)可以提供更好的并行性能。

所以听起来toConcurrentMap将并行化插入。

默认情况下,支持地图为HashMap。它只调用toMap的版本,该版本需要Supplier<M>并传递HashMap::new。 (来源:来源)

答案 1 :(得分:2)

  

我如何知道是否使用多个线程填充地图,即并行?

很难说。如果您的代码出乎意料慢慢,那可能是因为您尝试使用多个线程。

  

我是否需要调用Collectors.toConcurrentMap而不是Collectors.toMap?

这有助于提高并行效率,或者采用另一种方式,效率低一点。

  

这是一种合理的方法来并行化地图的人口吗?

你可以按照你的建议去做,但是你应该注意到,启动一个新线程的成本比你在这里做的所有事情都要贵得多,所以添加一个线程会使它减慢很多。

  

我如何知道具体地图支持新的niMap(例如是HashMap)?

文档说你无法确定。我最后一次检查toMap是使用HashMap而groupingBy使用的是LinkedHashMap,但您不能认为它是任何特定的地图。

答案 2 :(得分:1)

您可以将toConcurrentMap用于顺序流,将toMap用于并行流。差异是

    对于并行流,
  • toConcurrentMap()通常比顺序流
  • 更快 对于顺序流,
  • toMap()通常比并行流
  • 更快

如果你不知道你的流来自哪里,并希望在两种情况下都更快,你可以这样写:

Map<String, String> niMap = niStream.collect(
    niStream.isParallel() ? 
        Collectors.toConcurrentMap(NameId::getName, NameId::getId) :
        Collectors.toMap(NameId::getName, NameId::getId)
);

不同之处在于toConcurrentMap()CONCURRENT收集器,这意味着在当前实现中使用并发数据结构(ConcurrentHashMap),可以同时从不同的线程填充。对于顺序流,这会增加一些不必要的开销,但对于并行流,它比使用toMap()更快,因为在toMap()情况下,将为每个并行线程创建单独的非并发Map实例,然后将这些映射合并在一起这对于大型地图而言并不是很快。

请注意,我的StreamEx库增强了标准Stream API,它添加了一个toMap()方法,该方法对并行流使用并发集合,对顺序流使用非并发集合:

Map<String, String> niMap = StreamEx.of(niStream)
                      .toMap(NameId::getName, NameId::getId);