java 怎么去重
- 后端开发
- 2025-08-03
- 4342
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,
