本文目录导读:

在Java开发中,清理冗余数据(如重复记录、过期数据、无效数据)通常涉及内存中的数据清理和数据库中的数据清理,下面我将从这两个方面,结合具体的代码案例和常用技巧进行说明。
清理内存中的冗余数据(Java集合处理)
使用 Set 去重
Set 数据结构天然不允许重复元素,是清理内存中重复对象的首选。
案例:清理 List 中的重复对象
import java.util.*;
public class DuplicateRemovalExample {
public static void main(String[] args) {
List<String> listWithDuplicates = Arrays.asList("A", "B", "A", "C", "B", "D");
// 方法1:借助 HashSet 去重
Set<String> set = new HashSet<>(listWithDuplicates);
List<String> uniqueList = new ArrayList<>(set);
System.out.println("去重后: " + uniqueList); // 输出: [A, B, C, D] (顺序不保证)
// 方法2:使用 LinkedHashSet 保持插入顺序
Set<String> linkedSet = new LinkedHashSet<>(listWithDuplicates);
List<String> orderedUniqueList = new ArrayList<>(linkedSet);
System.out.println("有序去重: " + orderedUniqueList); // 输出: [A, B, C, D]
// 方法3:Java 8 Stream API
List<String> streamUnique = listWithDuplicates.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("Stream去重: " + streamUnique);
}
}
自定义对象去重(重写 equals 和 hashCode)
对于自定义对象,必须正确重写 equals() 和 hashCode(),否则 Set 无法识别重复。
class User {
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return id == user.id && Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
// getters...
}
// 使用
List<User> users = new ArrayList<>();
users.add(new User(1, "Alice"));
users.add(new User(2, "Bob"));
users.add(new User(1, "Alice")); // 重复
users.add(new User(3, "Charlie"));
List<User> uniqueUsers = new ArrayList<>(new HashSet<>(users));
System.out.println("去重后人数: " + uniqueUsers.size()); // 3
根据特定字段去重
有时我们不希望整个对象相同才算重复,而是根据某个字段(如ID)去重。
List<User> users = ...;
Map<Integer, User> uniqueMap = new LinkedHashMap<>();
for (User u : users) {
uniqueMap.putIfAbsent(u.getId(), u); // 按ID去重,保留首次出现
}
List<User> uniqueByField = new ArrayList<>(uniqueMap.values());
// 使用 Stream API
List<User> streamUnique = users.stream()
.filter(distinctByKey(User::getId)) // 需要自定义 Predicate
.collect(Collectors.toList());
// 辅助方法
public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Set<Object> seen = ConcurrentHashMap.newKeySet();
return t -> seen.add(keyExtractor.apply(t));
}
清理数据库中的冗余数据(JDBC / JPA 操作)
使用 SQL 直接清理
最直接、高效的方式是执行删除 SQL。
案例:删除重复记录(保留ID最小的那一条)
-- 删除 email 重复的记录,只保留每组中 id 最小的那一行
DELETE FROM users
WHERE id NOT IN (
SELECT MIN(id) FROM users GROUP BY email
);
在 JDBC 中执行
String sql = "DELETE FROM users WHERE id NOT IN (SELECT MIN(id) FROM users GROUP BY email)";
try (Statement stmt = connection.createStatement()) {
int deletedRows = stmt.executeUpdate(sql);
System.out.println("删除了 " + deletedRows + " 条重复记录");
}
使用 JPA / Hibernate 清理
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Modifying
@Transactional
@Query(value = "DELETE FROM users WHERE id NOT IN (SELECT MIN(id) FROM users GROUP BY email)", nativeQuery = true)
int deleteDuplicateByEmail();
}
清理过期数据(基于时间戳)
-- 删除7天前的日志 DELETE FROM logs WHERE created_at < NOW() - INTERVAL '7 days';
String deleteSql = "DELETE FROM logs WHERE created_at < :cutoff";
int deleted = jdbcTemplate.update(deleteSql,
Map.of("cutoff", LocalDateTime.now().minusDays(7)));
清理冗余文件 / 外部存储
如果冗余数据是文件系统中的临时文件、日志等:
import java.io.IOException;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.stream.Stream;
public class FileCleaner {
public static void cleanOldFiles(String dirPath, int daysToKeep) throws IOException {
Path dir = Paths.get(dirPath);
LocalDateTime cutoff = LocalDateTime.now().minusDays(daysToKeep);
try (Stream<Path> files = Files.list(dir)) {
files.filter(Files::isRegularFile)
.filter(path -> {
try {
LocalDateTime lastModified = Files.getLastModifiedTime(path).toInstant()
.atZone(java.time.ZoneId.systemDefault()).toLocalDateTime();
return lastModified.isBefore(cutoff);
} catch (IOException e) {
return false;
}
})
.forEach(path -> {
try {
Files.delete(path);
System.out.println("已删除过期文件: " + path);
} catch (IOException e) {
System.err.println("删除失败: " + path);
}
});
}
}
public static void main(String[] args) throws IOException {
cleanOldFiles("/tmp/logs", 30); // 删除30天前的文件
}
}
综合最佳实践建议
-
优先用 SQL 层面清理:数据库直接执行 DELETE 比将数据加载到 Java 中再处理快得多。
-
分页删除避免锁表:如果冗余数据量非常大,使用分页删除或限制每次删除的行数:
DELETE FROM table WHERE condition LIMIT 1000; -- MySQL
或在 Java 中循环执行小批量删除。
-
建立定时任务:使用 Spring
@Scheduled或 Quartz 定时清理:@Scheduled(cron = "0 0 3 * * ?") // 每天凌晨3点执行 public void scheduleCleanup() { // 调用清理逻辑 } -
对清理操作记录日志:记录删除数量、时间、影响行数,方便回溯问题。
-
先备份再清理:在清理重要数据前,建议将数据备份或软删除(如增加
deleted_at字段)。
| 场景 | 推荐方案 |
|---|---|
| 内存中 List 去重 | Set / Stream.distinct() / Map.putIfAbsent() |
| 集合根据字段去重 | ConcurrentHashMap.newKeySet() + 自定义 Predicate |
| 数据库重复记录 | SQL DELETE ... NOT IN (SELECT MIN(...)) |
| 数据库过期数据 | SQL WHERE created_at < 时间 |
| 文件系统清理 | NIO Files.list() + Files.delete() |
| 大规模数据清理 | 分页删除 + 定时任务 + 日志与告警 |
掌握这些案例,你就可以应对绝大多数 Java 项目中的冗余数据清理需求,如果有具体的业务场景(如清理缓存、清理 Kafka 队列),也可以进一步结合相应的 API 来处理。