我有以下代码尝试通过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)?
答案 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);