游乐游手机版
首页/编程语言/文章详情

如何利用 Stream.distinct() 去除集合流中的重复元素

时间:2026-05-04 22:19
如何利用 Stream distinct() 去除集合流中的重复元素 Stream distinct() 依赖对象的 equals() 和 hashCode() 这里有个关键点需要先搞清楚:distinct() 并不是对对象进行深度的值比较。它的底层逻辑,其实是调用每个元素的 equals() 和

如何利用 Stream.distinct() 去除集合流中的重复元素

如何利用 Stream.distinct() 去除集合流中的重复元素

Stream.distinct() 依赖对象的 equals() 和 hashCode()

这里有个关键点需要先搞清楚:distinct() 并不是对对象进行深度的值比较。它的底层逻辑,其实是调用每个元素的 equals()hashCode() 方法来判断是否重复。这意味着,如果你传入的是自定义对象(比如一个 User 类),但没有重写这两个方法,那么即使两个对象的字段内容完全一样,也会被当作不同的元素保留下来。

  • 对于 StringInteger 这类 JDK 内置类型,它们已经默认实现了正确的逻辑,可以直接使用。
  • 对于自定义类,就必须手动重写 equals()hashCode(),而且两者的逻辑必须保持一致。
  • 如果只重写了 equals() 却忘了 hashCode()distinct() 的行为可能会失效,或者变得不稳定,这可是个经典的坑。

distinct() 是有状态操作,不能并行流里随意替换顺序

distinct() 在内部使用了一个 LinkedHashSet 来缓存已经遇到过的元素。这个机制带来了两个特点:一是它会保留第一次出现的元素,二是它会尽力维持原始的顺序。然而,一旦进入并行流的世界,情况就变了。所谓的“第一次出现”,完全取决于线程的调度顺序,最终结果的一致性也就无法保证了。

  • 串行流Stream.of("a", "b", "a").distinct().toList() 的结果总是确定的 ["a", "b"]
  • 并行流Stream.of("a", "b", "a").parallel().distinct().toList() 的结果就不确定了,可能是 ["a", "b"],也可能是 ["b", "a"]
  • 如果业务场景要求并行处理、去重并且还要保持稳定顺序,通常的解决方案是先调用 sorted() 排序,或者干脆不使用 distinct(),转而采用 Collectors.toCollection(LinkedHashSet::new) 这类收集器。

distinct() 不适用于按某个字段去重的场景

这是另一个常见的误解。假设你有一个 List,想根据 userId 字段来去重,distinct() 是做不到的——它只能判断整个对象是否相等,无法让你指定按哪个字段去重。

  • 错误写法users.stream().distinct()。这依赖的是整个 User 对象的 equals() 方法,往往不是业务真正需要的语义。
  • 正确思路:通常需要借助 Collectors.toMap()Collectors.collectingAndThen(),配合 TreeSetLinkedHashMap 来实现。
  • 常用替代方案users.stream().collect(Collectors.toMap(User::getId, u -> u, (a, b) -> a)).values()。这个写法清晰表达了“以 id 为键,保留第一个遇到的元素”的意图。

性能和内存开销比想象中大

别小看 distinct() 的开销。因为它需要缓存所有已经遍历过的元素,在最坏的情况下(所有元素都不重复),其内存占用会与输入流的长度成正比。同时,每次遇到新元素都要去哈希表里查询一次,也有计算成本。

  • 面对大数据量(比如百万级别)时,这个缓存集合很容易引发内存溢出(OOM),或者显著拖慢整个处理流程的速度。
  • 如果去重只是为了统计数量,使用 stream.distinct().count() 不如 stream.collect(Collectors.toSet()).size() 来得清晰,而且后者对中间集合的生命周期有更明确的控制。
  • 如果上游数据已经是排序好的,其实可以手动编写逻辑来跳过相邻的重复项(类似于归并排序中的去重思路),从而完全避免额外的集合开销。

话说回来,在实际使用中,不能只满足于“代码能跑通”。重点要审视几个方面:对象是如何定义的、数据规模有多大、是否需要保留顺序、业务上是否真的需要整个对象级别的判重。这几个问题如果没理清楚,distinct() 很容易就会变成一个隐蔽的问题来源。

来源:https://www.php.cn/faq/2419148.html
上一篇Ubuntu下Golang项目如何管理依赖 下一篇如何在Ubuntu中使用Golang进行Web开发
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
CentOS与Golang打包常见兼容性问题探讨
编程语言 · 2026-07-01

CentOS与Golang打包常见兼容性问题探讨

CentOS与Golang打包的兼容性问题集中在glibc版本不匹配、交叉编译环境变量错误、依赖库缺失及Go依赖管理不规范。可通过Docker容器编译、选择兼容Go版本、正确设置GOOS GOARCH环境变量、安装对应开发包及使用GoModules解决。

CentOS中Fortran与Python如何协同工作从入门到实战完整教程
编程语言 · 2026-07-01

CentOS中Fortran与Python如何协同工作从入门到实战完整教程

在CentOS中,Fortran与Python可通过f2py、SWIG、共享库调用或subprocess协同。f2py封装Fortran为Python模块,支持数组运算;共享库需手动对齐数据类型;系统调用适合独立计算。

CentOS中Golang打包优化方法
编程语言 · 2026-07-01

CentOS中Golang打包优化方法

在CentOS中优化Golang编译打包,可显著提升编译速度并减小二进制文件体积。关键技巧包括:设置环境变量、使用Go模块管理依赖、编译时添加-ldflags= "-s-w "去除调试信息、利用UPX工具压缩、运行strip清理符号表,以及优化cgo内C代码的编译选项。综合运用这些方法能有效优化最终程序。

在CentOS系统中cpustat与其他工具协同使用的完整方法
编程语言 · 2026-07-01

在CentOS系统中cpustat与其他工具协同使用的完整方法

cpustat作为sysstat包的CPU监控工具,可通过管道与grep等命令配合过滤数据,利用脚本自动记录带时间戳的日志,或结合图形工具查看,也可格式化输出后接入Zabbix、Grafana等Web监控系统,实现可视化与告警。

CentOS中readdir与其他Linux发行版的差异
编程语言 · 2026-07-01

CentOS中readdir与其他Linux发行版的差异

CentOS基于RHEL,与Ubuntu、Debian、Fedora在包管理器(yum dnfvsapt)、默认文件系统(XFSvsext4)等存在差异,但readdir等系统调用遵循POSIX标准,行为一致。