
本文介绍一种基于 HashSet 的高效方式,通过重写 equals() 和 hashCode() 实现按邮箱+手机号去重,避免嵌套遍历,将合并时间复杂度从 O(n×m) 优化至接近 O(n+m)。
本文介绍一种基于 `hashset` 的高效方式,通过重写 `equals()` 和 `hashcode()` 实现按邮箱+手机号去重,避免嵌套遍历,将合并时间复杂度从 o(n×m) 优化至接近 o(n+m)。
在实际业务中(如客户数据同步、批量导入等场景),常需将新获取的客户列表与数据库已有列表合并,并确保不重复插入——而“重复”的判定逻辑往往并非简单对象引用或 ID 相等,而是基于业务唯一键,例如 邮箱 + 手机号 的组合。此时,若直接使用 List.contains() 进行逐个比对(如原代码中 testObjects.contains(client)),会导致每次检查都需遍历整个原始列表,整体时间复杂度为 O(n × m),在数据量较大时性能急剧下降。
更优解是利用哈希集合(HashSet)的 O(1) 平均查找性能。关键前提是:正确重写 equals() 和 hashCode() 方法,使其仅依据 email 和 phoneNubmer 字段判断相等性——这已在示例中实现,是整个方案成立的基础。
以下是推荐的简洁、高效实现方式:
// 原有数据库客户列表(List)
List<testobject> existingClients = new ArrayList(List.of(
new TestObject(1, "+111", "email1", "Ivan"),
new TestObject(2, "+222", "email2", "Ilya"),
new TestObject(3, "+333", "email3", "Pavel")
));
// 新增客户列表(转为 Set,自动去重且支持快速查找)
Set<testobject> newClients = new HashSet(List.of(
new TestObject(null, "+444", "email4", "Anna"), // ✅ 新客户
new TestObject(null, "+111", "email1", "Ivan"), // ❌ 已存在(同手机号+邮箱)
new TestObject(null, "+222", "email2", "Ivan") // ❌ 已存在
));
// 步骤1:从 newClients 中移除所有已在 existingClients 中存在的客户
newClients.removeAll(existingClients);
// 步骤2:将剩余的唯一新客户添加到原列表(若允许修改原列表)
existingClients.addAll(newClients);
// 或者:创建全新合并列表(推荐,保持原始数据不可变)
List<testobject> mergedClients = new ArrayList(existingClients);
mergedClients.addAll(newClients);</testobject></testobject></testobject>
✅ 优势说明:
- removeAll() 底层调用 HashSet 的 contains(),依赖 hashCode() 定位桶 + equals() 精确比对,平均单次查找为 O(1);
- 整体复杂度降为 O(n + m),远优于流式 filter + contains 的 O(n × m);
- 语义清晰:removeAll 直观表达“剔除已存在项”,逻辑更易维护。
⚠️ 注意事项:
- 必须确保 TestObject.equals() 和 hashCode() 严格一致,且只依赖 email 和 phoneNubmer(已空安全处理,但建议在 equals() 中显式处理 null,如 Objects.equals(email, that.email) 已满足);
- 若 newClients 中本身存在邮箱+手机号相同的重复对象(如两个 "+111"/"email1"),HashSet 会自动去重——这是额外收益;
- 如需保留 newClients 原始顺序,可改用 LinkedHashSet;
- 生产环境建议封装为通用工具方法,例如:
public static <t> List<t> mergeWithoutDuplicates(List<t> base, Collection<t> toAdd) { Set<t> baseSet = new HashSet(base); return base.stream() .collect(Collectors.collectingAndThen( Collectors.toList(), list -> { list.addAll(toAdd.stream().filter(e -> !baseSet.contains(e)).toList()); return list; } )); }</t></t></t></t></t>
综上,使用 HashSet 替代 List 存储待合并数据,并配合 removeAll(),是以最小代码改动换取显著性能提升的优雅实践。











