java 怎么去重
- 后端开发
- 2025-08-03
- 4307
Java编程中,去重是一个常见的需求,尤其是在处理集合数据时,以下是几种常用的方法及其详细实现方式:
使用Set特性自动去重
Java中的Set
接口天生具备不允许重复元素的特性(基于equals()
方法和哈希码判断唯一性),这是最简单直接的方式。
| 实现类型 | 特点 | 适用场景 |
|—————-|———————————————————————-|——————————|
| HashSet
| 无序存储,性能高;不保证插入顺序 | 对顺序无要求的场景 |
| LinkedHashSet
| 维护元素的插入顺序,遍历时按添加顺序输出 | 需要保留原始顺序的情况 |
| TreeSet
| 自动排序(自然序或自定义比较器),有序集合 | 需要排序后的去重结果 |
示例代码:
import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import java.util.TreeSet; public class SetDeduplication { public static void main(String[] args) { Integer[] numbers = {3, 1, 4, 1, 5, 9, 2, 6, 5}; // 普通去重(无序) Set<Integer> hashSet = new HashSet<>(List.of(numbers)); System.out.println("HashSet结果: " + hashSet); // 输出类似 [1, 2, 3, 4, 5, 6, 9] // 保留顺序的去重 Set<Integer> linkedHashSet = new LinkedHashSet<>(List.of(numbers)); System.out.println("LinkedHashSet结果: " + linkedHashSet); // 按插入顺序输出 [3, 1, 4, 5, 9, 2, 6] // 排序后的去重 Set<Integer> treeSet = new TreeSet<>(List.of(numbers)); System.out.println("TreeSet结果: " + treeSet); // 升序排列 [1, 2, 3, 4, 5, 6, 9] } }
️注意:如果对象未正确重写hashCode()
和equals()
方法,可能导致错误的去重行为,例如自定义类作为元素时,必须覆盖这两个方法以确保逻辑一致性。
Stream API流式处理
Java 8引入的Stream API提供了声明式的数据处理能力,结合distinct()
中间操作可实现优雅的去重。
基础用法(针对基本类型/不可变对象):
List<String> originalList = Arrays.asList("apple", "banana", "apple", "orange"); List<String> distinctList = originalList.stream().distinct().toList(); // distinctList → ["apple", "banana", "orange"]
复杂对象去重技巧:
当处理自定义对象时,需确保其正确实现了equals()
方法。
record Person(String name, int age) {} // Java 16+记录类自动生成equals/hashCode List<Person> people = List.of( new Person("Alice", 30), new Person("Bob", 25), new Person("Alice", 30) // 与第一个对象相等 ); List<Person> uniquePeople = people.stream() .distinct() .collect(Collectors.toList()); // uniquePeople大小变为2
分组统计扩展应用:
若需同时获取重复次数等信息,可配合Collectors.groupingBy
使用:
Map<String, Long> frequencyMap = originalList.stream() .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); // {"apple":2, "banana":1, "orange":1}
手动迭代控制流程
对于特殊场景(如保持特定过滤逻辑),可以通过传统循环实现精细化控制,以下是两种典型模式:
方案1:双指针法(原地修改数组)推荐用于面试算法题
适用于已排序数组的原地去重,空间复杂度O(1):
public int removeDuplicates(int[] nums) { if (nums.length == 0) return 0; int slow = 0; for (int fast = 1; fast < nums.length; fast++) { if (nums[fast] != nums[slow]) { slow++; nums[slow] = nums[fast]; } } return slow + 1; // 新长度 }
输入示例:[1,1,2,3,3,4]
→ 处理后前4个元素变为[1,2,3,4]
,返回值4。
方案2:辅助集合标记法(通用型解决方案)
适合任意类型的List结构:
public static <T> List<T> manualDeduplicate(List<T> list) { Set<T> seen = new HashSet<>(); List<T> result = new ArrayList<>(); for (T item : list) { if (!seen.contains(item)) { seen.add(item); result.add(item); } } return result; }
此方法的优势在于:①可中途中断循环;②支持动态添加额外条件(如跳过某些特定值)。
数据库层面的优化策略
当数据源来自数据库时,应在SQL层面完成去重操作以提高效率,常见写法包括:
| 语法类型 | 示例 | 说明 |
|————————|————————————–|————————–|
| SELECT DISTINCT ...
| SELECT DISTINCT department FROM employees;
| 基础去重语句 |
| 窗口函数 | SELECT id, department, ROW_NUMBER() OVER (PARTITION BY department ORDER BY id) as rn FROM employees;
| 配合WHERE子句过滤重复项 |
| GROUP BY聚合 | SELECT department, COUNT() FROM employees GROUP BY department;
| 同时完成计数功能 |
特别注意:对于海量数据集,数据库索引可以显著提升DISTINCT
操作的速度,建议为相关字段建立索引。
第三方库增强方案
某些场景下可以使用成熟工具库简化开发:
- Apache Commons Collections的
CollectionUtils
提供便捷的集合操作工具类; - Guava Multiset支持带计数的多重集合,适合需要知道元素出现频率的场景;
- Reactor Project的反应式编程模型可在异步流中实现实时去重。
FAQs
Q1:为什么用HashSet去重后元素的顺序改变了?
A: 因为HashSet
基于哈希表实现,不保证存储顺序,若需维持插入顺序,应改用LinkedHashSet
;若需要排序结果,则选择TreeSet
,这三种Set的区别本质上是对内部数据结构的取舍:速度、顺序还是排序能力。
Q2:如何对包含null值的列表进行安全去重?
A: 标准集合类允许单个null元素存在(如HashSet
),但多个null会被视为相同值,处理方法有两种:①预处理时过滤所有null值再进行去重;②使用特殊设计的Comparator显式处理null情况,示例代码如下:
List<String> withNulls = Arrays.asList("a", null, "b", null); Set<String> safeSet = new LinkedHashSet<>(withNulls); // 只保留一个null // safeSet → ["a", null,