HashSet去重快但不保序;Stream.distinct()保序需注意类型转换和自定义对象的equals/hashCode;数组不可原地去重;性能差异小,关键在正确用法。

用 HashSet 去重数组:快但丢顺序
原始数组转 HashSet 能秒去重,因为底层是哈希表,插入时自动过滤重复值。但注意:HashSet 不保证插入顺序,哪怕你按 [1,2,2,3] 放进去,迭代出来可能是 [2,1,3] —— 这在需要保持首次出现顺序的场景里就是错的。
- 适用于:只要结果无重复、不关心顺序,比如校验唯一性、生成 ID 集合
- 别直接用
new HashSet(Arrays.asList(arr))处理基本类型数组(如int[]),它会把整个数组当一个元素塞进去;必须先转成Integer[]或用Stream - 如果原数组很大,
HashSet会多占一倍内存,因为要额外存一份去重后数据
Stream.distinct() 保持顺序但要注意类型
Stream.distinct() 是 Java 8+ 最自然的顺序去重方式,底层依赖元素的 equals() 和 hashCode()。对 String、包装类等默认实现没问题,但自定义对象必须重写这两个方法,否则所有对象都算“不同”——看着没去重,其实是没生效。
- 基本类型数组得先用
Arrays.stream(arr).boxed()转成流,比如int[] nums = {1,2,2,3}; Arrays.stream(nums).boxed().distinct().toArray(Integer[]::new) - 字符串数组可直接用:
Arrays.stream(strArr).distinct().toArray(String[]::new) - 性能上比
HashSet略低一点,因为要遍历 + 判重,但对几千以内元素几乎无感
原地去重?Java 数组长度不可变,别硬改
有人想“像 C++ std::unique 那样原地挪动”,但在 Java 里行不通。数组创建后 length 就固定了,distinct() 或 HashSet 返回的都是新数组或新集合。强行用循环覆盖旧数组后半部分,不仅代码难读,还容易漏掉边界判断,比如 arr[i] = arr[j] 时 j 越界。
- 真要节省内存,优先选
Stream.distinct().toList()得到List,而不是死磕数组 - 如果必须返回数组,老实用
.toArray(),别试图复用原数组空间 - 注意
distinct()对null值友好,但HashSet也允许一个null,两者行为一致
性能差异实际没那么大,别过早优化
除非数组超十万级,否则 HashSet 和 Stream.distinct() 的耗时差不到 1ms。真正拖慢的往往是对象的 equals() 实现——比如你在去重 User 对象时,equals() 里查了数据库,那瓶颈根本不在去重逻辑本身。
立即学习“Java免费学习笔记(深入)”;
- 测性能前先确认:是不是每次都在重复创建流?避免在循环里反复调
Arrays.stream(...).distinct() - 如果只是临时去重打印调试,用
System.out.println(new HashSet(Arrays.asList(arr)))最省事 - 多线程环境下,别共享同一个
HashSet实例去重,要用ConcurrentHashMap.newKeySet()替代










