本文目录导读:

- 目录导读
- 方法一:利用HashSet特性去重(最常用)
- 方法二:使用Java 8 Stream API的distinct()
- 方法三:通过LinkedHashSet保持原顺序去重
- 方法四:借助TreeSet实现排序去重
- 方法五:手动遍历与Map去重(自定义场景)
- 常见问答(Q&A)
- 性能对比与选择建议
Java案例精讲:如何高效去除集合中的重复值?5种实战方法详解
目录导读
- 集合重复数据问题的普遍性与处理价值
- 利用HashSet特性去重(最常用)
- 使用Java 8 Stream API的distinct()
- 通过LinkedHashSet保持原顺序去重
- 借助TreeSet实现排序去重
- 手动遍历与Map去重(自定义场景)
- 常见问答(Q&A)
- 性能对比与选择建议
在实际项目开发中,集合(List、Set等)中出现重复值是非常常见的问题,从数据库查询到的数据、用户输入列表、文件读取内容等场景,数据冗余不仅浪费内存,还可能影响业务逻辑的正确性,掌握多种去重方案是Java开发者的基本功。
本文将通过5个真实案例,详细讲解不同场景下如何高效去除集合重复值,涵盖从基础到高级(Java 8+)的方法,并附上性能对比和常见问题解答。
利用HashSet特性去重(最常用)
案例场景
假设有一个List<String>包含重复的姓名,需要去除重复元素。
实现代码
import java.util.*;
public class RemoveDuplicates1 {
public static void main(String[] args) {
List<String> list = new ArrayList<>(Arrays.asList("张三", "李四", "张三", "王五", "李四"));
// 将List转成HashSet,自动去重
Set<String> set = new HashSet<>(list);
// 如果需要返回List,再转回来
List<String> result = new ArrayList<>(set);
System.out.println(result); // 输出:[李四, 张三, 王五](无序)
}
}
原理分析
HashSet底层基于哈希表,利用hashCode()和equals()方法判断元素是否重复,插入时,若元素已存在,则拒绝添加,整个过程时间复杂度为O(n),效率极高。
注意事项
- 不保证原顺序,如果依赖顺序请使用方法三。
- 自定义对象必须重写
equals()和hashCode()。
使用Java 8 Stream API的distinct()
案例场景
同样处理上述姓名列表,但希望代码更简洁(Java 8+环境)。
实现代码
import java.util.*;
import java.util.stream.Collectors;
public class RemoveDuplicates2 {
public static void main(String[] args) {
List<String> list = Arrays.asList("张三", "李四", "张三", "王五", "李四");
List<String> result = list.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(result); // 输出:[张三, 李四, 王五](保留首次出现顺序)
}
}
原理分析
distinct()是Stream API中的中间操作,内部通过LinkedHashSet实现去重,因此能保留原顺序,它是处理有序去重的首选方案之一。
优势
- 函数式编程,代码优雅。
- 支持链式操作(如过滤、映射后去重)。
通过LinkedHashSet保持原顺序去重
案例场景
需要去重,同时必须保持元素插入的原始顺序。
实现代码
import java.util.*;
public class RemoveDuplicates3 {
public static void main(String[] args) {
List<String> list = Arrays.asList("张三", "李四", "张三", "王五", "李四");
Set<String> set = new LinkedHashSet<>(list);
List<String> result = new ArrayList<>(set);
System.out.println(result); // 输出:[张三, 李四, 王五](顺序保留)
}
}
原理分析
LinkedHashSet继承自HashSet,但内部维护了一个双向链表,用于记录元素的插入顺序,因此去重同时能稳定保留原顺序,适合对顺序敏感的UI列表、数据模板等场景。
借助TreeSet实现排序去重
案例场景
去重的同时希望元素自然排序(如数字从小到大,字符串字典序)。
实现代码
import java.util.*;
public class RemoveDuplicates4 {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(3, 1, 2, 1, 4, 3, 5);
Set<Integer> sortedSet = new TreeSet<>(numbers);
System.out.println(sortedSet); // 输出:[1, 2, 3, 4, 5](排序且去重)
}
}
原理分析
TreeSet底层是红黑树(自平衡二叉查找树),元素按自然顺序或自定义Comparator排序,插入和查找复杂度为O(log n),适合需要有序结果集的场景。
自定义排序
Set<Person> set = new TreeSet<>((a, b) -> a.age - b.age);
手动遍历与Map去重(自定义场景)
案例场景
需要根据对象的某个字段去重(如按用户ID去重,保留最新一条)。
实现代码
import java.util.*;
class User {
int id;
String name;
User(int id, String name) { this.id = id; this.name = name; }
// getter 省略
}
public class RemoveDuplicates5 {
public static void main(String[] args) {
List<User> users = Arrays.asList(
new User(1, "张三"),
new User(2, "李四"),
new User(1, "张三(重复)") // ID相同,视为重复
);
Map<Integer, User> map = new HashMap<>();
for (User u : users) {
map.putIfAbsent(u.id, u); // 仅当Key不存在时放入
}
List<User> result = new ArrayList<>(map.values());
System.out.println(result.size()); // 输出:2
}
}
原理分析
利用Map的Key唯一性,可自由控制保留策略(如覆盖、保留第一条等),适用于复杂业务去重(如按多个字段组合去重)。
常见问答(Q&A)
Q1:HashSet和LinkedHashSet哪个去重效率更高?
A:HashSet效率略高(O(1)),因为它不需要维护链表,但如果需要保留顺序,使用LinkedHashSet,其额外开销很小。
Q2:Stream distinct()会改变原集合吗?
A:不会,Stream操作返回新集合,原集合保持不变,如果需要修改原变量,需重新赋值。
Q3:自定义对象去重必须重写equals和hashCode吗?
A:是的,否则Object类的equals默认比较内存地址,会导致对象即使内容相同也被看作不同元素。
Q4:TreeSet去重时,比较器返回0为什么会被视为重复?
A:TreeSet认为compareTo()返回0表示两个元素“相等”,因此不会插入,注意这与equals()是否重写无关。
Q5:对于大数据量(百万级)去重,哪种方法推荐?
A:推荐HashSet(无序)或Stream.distinct()(有序),因为它们是O(n)复杂度,避免使用TreeSet(O(n log n))在大数据量下性能下降。
性能对比与选择建议
| 方法 | 时间复杂度 | 是否保留顺序 | 适用场景 |
|---|---|---|---|
| HashSet | O(n) | 否 | 对顺序无要求,追求速度 |
| Stream distinct() | O(n) | 是 | Java 8+,代码简洁 |
| LinkedHashSet | O(n) | 是 | 需要保留插入顺序 |
| TreeSet | O(n log n) | 是(排序) | 需要排序结果 |
| Map手动遍历 | O(n) | 视实现而定 | 复杂字段去重 |
最终建议:
- 简单去重:
List → Set → List即可。 - 保持顺序:用
Stream.distinct()或LinkedHashSet。 - 自定义规则:使用
Map+putIfAbsent或Stream+Collectors.toMap。