Java Stream中用filter+Set组合替代distinct获取唯一元素的性能探究
用ConcurrentHashMap替代Stream.distinct()的并行化与性能分析
核心逻辑差异
Stream自带的distinct()是有状态中间操作,并行执行时会先在每个线程本地维护独立的去重集合,最后再合并所有线程的结果做全局去重——整个过程由Stream框架自动处理,无需开发者操心线程安全。
而你用ConcurrentHashMap.newKeySet()配合filter(seen::add)的方式,是把去重逻辑绑定到了一个共享的线程安全集合上,每个元素都要调用线程安全的add()方法完成去重判断。
并行化表现:反而拉低效率
在并行流场景下,这种自定义方式的问题非常突出:
- 每个线程处理元素时,都要对共享的ConcurrentHashMap执行原子操作。虽然ConcurrentHashMap用了分段锁优化高并发,但大量线程同时操作还是会引发锁竞争,导致线程等待,直接抵消并行带来的性能提升。
- 原生
distinct()通过本地先去重的策略,大幅降低了全局合并阶段的工作量,线程间协作成本更低,并行执行的效率反而更高。
性能对比:多数场景不如原生实现
- 串行场景:原生
distinct()使用普通HashSet(无线程安全开销),而ConcurrentHashMap的线程安全机制会带来额外性能损耗,所以串行下自定义方式的性能必然不如原生。 - 并行场景:如上述所说,锁竞争会拖慢执行速度。只有在元素重复率极高、且线程数量极少的极端场景下,两者性能差距可能不大,但绝大多数业务场景中,原生
distinct()的表现更优。
额外隐患:结果顺序不可控
如果原始流是有序的(比如ArrayList的流),原生distinct()在并行执行时会尽量保留元素的原始出现顺序。但用filter(seen::add)的方式,因为并行线程调度的不确定性,最终收集到的List元素顺序可能和原始流完全不一致,容易引入意料之外的逻辑bug。
总结
不推荐用这种方式替代distinct()。无论是并行化效率还是整体性能,原生distinct()都更有优势,而且框架已经封装好了线程安全、结果合并等细节,代码也更简洁易读。
内容的提问来源于stack exchange,提问作者Travis Zhang
相关产品推荐
相关产品推荐

