如何利用Java Stream高效实现两个Map<Long, Collection<Person>>的比较与重复人员数据过滤
优化Map比较逻辑:用Java Stream实现高效简洁的写法
首先,咱们先聊聊你原来代码里的几个问题:
- 四层嵌套循环效率极低:时间复杂度是O(MNP*Q)(M是currMap的entry数,N是prevMap的entry数,P/Q是对应entry里的Person数量),当数据量稍大时性能会急剧下降。
- 逻辑判断错误:你的条件
(!currPerson.getName().equals(prevPerson.getName()) && !currPerson.getSurname().equals(prevPerson.getSurname()))是说「name不同并且surname不同」才添加Person,这和你的需求完全相反——你需要排除的是「name和surname都相同」的Person,正确逻辑应该是:只要prev中存在任何一个和currPerson的name+surname都匹配的Person,就不保留这个currPerson。 - 结果收集逻辑错误:你在循环外创建了一个全局的
arr,会把所有符合条件的Person都塞进同一个列表,然后重复put同一个key,导致不同key下的Person混在一起,结果完全不符合预期。
高效优化方案思路
核心优化点是提前把prevMap中所有Person的唯一标识(name+surname)存入一个Set,这样后续判断是否存在匹配的时间复杂度是O(1),整体时间复杂度降到O(P + C)(P是prev中总Person数,C是curr中总Person数),效率提升非常明显。
然后用Stream API来实现过滤和收集,代码会更简洁易读。
具体实现代码
第一步:定义Person的唯一标识类(或用Record)
为了把name和surname作为一个整体存入Set,我们需要一个能正确实现equals()和hashCode()的类。Java 16+可以用Record(简洁方便),低于16的话可以自定义类:
// Java 16+ 推荐用Record,自动实现equals和hashCode private record PersonKey(String name, String surname) {} // 兼容Java 16以下的自定义类 /* private static class PersonKey { private final String name; private final String surname; public PersonKey(String name, String surname) { this.name = name; this.surname = surname; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PersonKey personKey = (PersonKey) o; return Objects.equals(name, personKey.name) && Objects.equals(surname, personKey.surname); } @Override public int hashCode() { return Objects.hash(name, surname); } } */
第二步:提取prevMap的Person标识到Set
Set<PersonKey> prevPersonKeys = prevMap.values().stream() .flatMap(Collection::stream) // 把所有entry里的Collection扁平化成Person流 .map(p -> new PersonKey(p.getName(), p.getSurname())) // 转成PersonKey .collect(Collectors.toSet()); // 存入Set,自动去重
第三步:用Stream处理currMap生成结果
Map<Long, Collection<Person>> result = currMap.entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, // 保留原key entry -> entry.getValue().stream() .filter(p -> !prevPersonKeys.contains(new PersonKey(p.getName(), p.getSurname()))) // 过滤掉在prev中存在的Person .collect(Collectors.toList()) // 收集成List,也可以用toCollection指定集合类型 ));
如果需要过滤掉结果中为空的Collection(比如某个key下所有Person都被排除了,就不保留这个key),可以加一步过滤:
Map<Long, Collection<Person>> result = currMap.entrySet().stream() .map(entry -> Map.entry( entry.getKey(), entry.getValue().stream() .filter(p -> !prevPersonKeys.contains(new PersonKey(p.getName(), p.getSurname()))) .collect(Collectors.toList()) )) .filter(entry -> !entry.getValue().isEmpty()) // 去掉空集合的entry .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
完整示例测试
结合你给出的示例数据,完整代码如下:
import java.util.*; import java.util.stream.Collectors; public class PersonMapComparison { static class Person { private String name; private String surname; private int age; private String gender; public Person(String name, String surname, int age, String gender) { this.name = name; this.surname = surname; this.age = age; this.gender = gender; } public String getName() { return name; } public String getSurname() { return surname; } @Override public String toString() { return "Person{name='" + name + "', surname='" + surname + "', age=" + age + ", gender='" + gender + "'}"; } } private record PersonKey(String name, String surname) {} public static void main(String[] args) { // 初始化示例数据 Collection<Person> currPersons = List.of(new Person("John", "Smith", 20, "m")); Map<Long, Collection<Person>> currMap = Map.of(1L, currPersons); Collection<Person> prevPersons = List.of( new Person("John", "Smith", 20, "m"), new Person("Sarah", "Smith", 27, "f") ); Map<Long, Collection<Person>> prevMap = Map.of(1L, prevPersons); // 提取prev的Person标识 Set<PersonKey> prevPersonKeys = prevMap.values().stream() .flatMap(Collection::stream) .map(p -> new PersonKey(p.getName(), p.getSurname())) .collect(Collectors.toSet()); // 生成结果 Map<Long, Collection<Person>> result = currMap.entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, entry -> entry.getValue().stream() .filter(p -> !prevPersonKeys.contains(new PersonKey(p.getName(), p.getSurname()))) .collect(Collectors.toList()) )); // 打印结果(此时结果中key=1的集合为空,因为John Smith在prev中存在) result.forEach((key, persons) -> { System.out.println("Key: " + key); persons.forEach(System.out::println); }); } }
额外说明
- 如果你的
Person类本身的equals()和hashCode()就是基于name和surname实现的(不包含age、gender等其他字段),那可以直接把Person存入Set,不需要额外定义PersonKey。 - 如果需要保留原Collection的类型(比如
LinkedList而不是ArrayList),可以把Collectors.toList()换成Collectors.toCollection(LinkedList::new)。
内容的提问来源于stack exchange,提问作者cutelittlegnome
相关产品推荐
相关产品推荐

