java去重常用distinct()和set,二者均依赖equals+hashcode,但distinct()惰性求值、保序、支持并行;hashset不保序,linkedhashset保序,treeset排序;distinct()无法按字段去重,需配合其他api。

Java 中去重最常用的是 distinct() 和基于 Set(如 HashSet、LinkedHashSet)的手动去重,二者表面结果相似,但底层逻辑、适用场景和行为细节差异明显。关键不在“能不能去重”,而在“怎么去”“何时去”“顺序保不保”“对象认不认”。
去重依据:equals + hashCode 是共同底线
无论用 distinct() 还是 Set,判断两个元素是否重复,都严格依赖 equals() 和 hashCode() 的实现:
- 对
String、Integer等 JDK 类型,这两个方法已正确重写,开箱即用; - 对自定义对象(如
Person),若没重写equals()和hashCode(),哪怕字段值完全相同,也会被当作不同对象——去重失效; - 二者都要求这两个方法满足一致性:相等的对象必须有相同哈希码,哈希码相同的对象应尽量
equals()成立。
执行时机与数据流特性:惰性 vs 立即
distinct() 是 Stream 的中间操作,遵循惰性求值:
- 调用
.distinct()不会立刻遍历或去重,只构建处理管道; - 真正执行发生在终止操作(如
collect()、forEach())触发时; - 可与其他操作(
filter、map)链式组合,且能利用短路优化(如配合limit()提前结束)。而
new HashSet(list)是立即执行:- 构造时就遍历整个集合,一次性完成去重并装入新结构;
- 无法中断或延迟,也不支持流式链式处理;
- 适合简单、确定、小到中等规模的数据一次性转换。
顺序保持能力:distinct 保留首次出现,Set 实现各不相同
顺序是否保留,取决于具体实现:
-
Stream.distinct()总是保持原始顺序,且保留每个重复组中**第一个出现的元素**; -
HashSet不保证顺序(底层基于哈希桶,遍历时顺序不确定); -
LinkedHashSet保持插入顺序,效果与distinct()接近; -
TreeSet按自然序或自定义比较器排序,会彻底打乱原始顺序。
内存与扩展性:并行支持与状态管理差异
distinct()在并行流中有明确优化路径:-
parallelStream().distinct()内部使用分段ConcurrentHashMap或合并策略,可利用多核; - 但要注意:并行去重仍需全局唯一性保障,不能简单按分区独立去重后拼接(否则跨分区重复会被漏掉),JDK 已处理该逻辑;
-
Set构造本身是单线程操作,若想并行,需手动拆分+合并+再去重,复杂度高且易出错。内存方面:
- 两者都需要额外空间存储“已见元素”,空间复杂度均为 O(n);
- 对于超大数据流(如文件流、数据库游标),
distinct()无法增量去重(必须缓存所有已见元素),此时Set同样受限; - 若业务只需按某字段去重(如只看
name不看age),distinct()无法直接支持,需配合Collectors.toMap()或辅助Set手动实现。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











