本文目录导读:

我来给你详细讲解Java中过期数据的清理方案,并提供具体的代码示例。
使用定时任务清理过期数据
Timer + TimerTask
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class ExpiredDataCleaner {
// 存储带过期时间的数据
private static Map<String, CacheEntry> cache = new ConcurrentHashMap<>();
static class CacheEntry {
String data;
long expireTime;
CacheEntry(String data, long expireTime) {
this.data = data;
this.expireTime = expireTime;
}
}
public static void main(String[] args) {
// 添加测试数据
cache.put("key1", new CacheEntry("value1", System.currentTimeMillis() + 5000));
cache.put("key2", new CacheEntry("value2", System.currentTimeMillis() + 10000));
cache.put("key3", new CacheEntry("value3", System.currentTimeMillis() + 3000));
// 启动定时清理任务
Timer timer = new Timer(true);
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
cleanExpiredData();
}
}, 0, 2000); // 每2秒执行一次
System.out.println("开始监控过期数据...");
// 让程序运行一会儿观察效果
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
timer.cancel();
System.out.println("最终剩余数据: " + cache.size());
}
private static void cleanExpiredData() {
long currentTime = System.currentTimeMillis();
List<String> expiredKeys = new ArrayList<>();
// 找出过期数据的key
for (Map.Entry<String, CacheEntry> entry : cache.entrySet()) {
if (entry.getValue().expireTime <= currentTime) {
expiredKeys.add(entry.getKey());
}
}
// 删除过期数据
for (String key : expiredKeys) {
cache.remove(key);
System.out.println("清理过期数据: " + key + " at " + new Date());
}
System.out.println("当前缓存大小: " + cache.size());
}
}
ScheduledExecutorService(推荐)
import java.util.concurrent.*;
public class ScheduledCleaner {
private final ConcurrentHashMap<String, CacheItem> cache = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
static class CacheItem {
String value;
long timestamp;
CacheItem(String value, long timestamp) {
this.value = value;
this.timestamp = timestamp;
}
}
public ScheduledCleaner() {
// 启动周期性的清理任务
scheduler.scheduleAtFixedRate(
this::cleanExpired,
0,
1,
TimeUnit.SECONDS // 每秒执行一次
);
}
public void put(String key, String value, long ttl, TimeUnit unit) {
long expireTime = System.currentTimeMillis() + unit.toMillis(ttl);
cache.put(key, new CacheItem(value, expireTime));
}
public String get(String key) {
CacheItem item = cache.get(key);
if (item == null) return null;
// 检查是否过期
if (System.currentTimeMillis() > item.timestamp) {
cache.remove(key);
return null;
}
return item.value;
}
private void cleanExpired() {
long now = System.currentTimeMillis();
cache.entrySet().removeIf(entry -> now > entry.getValue().timestamp);
}
public void shutdown() {
scheduler.shutdown();
}
public static void main(String[] args) throws InterruptedException {
ScheduledCleaner cleaner = new ScheduledCleaner();
// 添加测试数据
cleaner.put("key1", "value1", 3, TimeUnit.SECONDS);
cleaner.put("key2", "value2", 5, TimeUnit.SECONDS);
// 模拟查询
for (int i = 0; i < 10; i++) {
System.out.println("第" + (i+1) + "秒 - key1: " + cleaner.get("key1") +
", key2: " + cleaner.get("key2"));
Thread.sleep(1000);
}
cleaner.shutdown();
}
}
使用缓存框架(推荐)
Guava Cache
import com.google.common.cache.*;
import java.util.concurrent.TimeUnit;
public class GuavaCacheExample {
public static void main(String[] args) throws InterruptedException {
// 创建缓存
Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(100) // 最大缓存数量
.expireAfterWrite(3, TimeUnit.SECONDS) // 写入后3秒过期
.expireAfterAccess(2, TimeUnit.SECONDS) // 访问后2秒过期
.recordStats() // 记录统计信息
.removalListener((RemovalNotification<String, String> notification) -> {
System.out.println(notification.getKey() + " 被移除,原因: " + notification.getCause());
})
.build();
// 添加数据
cache.put("key1", "value1");
cache.put("key2", "value2");
// 查看缓存
System.out.println("key1: " + cache.getIfPresent("key1"));
// 等待过期
Thread.sleep(5000);
System.out.println("5秒后 key1: " + cache.getIfPresent("key1"));
System.out.println("缓存统计: " + cache.stats());
}
}
Caffeine Cache(高性能)
import com.github.benmanes.caffeine.cache.*;
import java.util.concurrent.TimeUnit;
public class CaffeineCacheExample {
public static void main(String[] args) throws InterruptedException {
// 创建缓存
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(3, TimeUnit.SECONDS)
.expireAfterAccess(2, TimeUnit.SECONDS)
.removalListener((key, value, cause) -> {
System.out.println(key + " 被移除: " + cause);
})
.recordStats()
.build();
// 添加数据
cache.put("key1", "value1");
// 获取数据(自动处理过期)
String value = cache.getIfPresent("key1");
System.out.println("立即获取: " + value);
// 等待过期
Thread.sleep(4000);
System.out.println("4秒后: " + cache.getIfPresent("key1"));
// 查看统计
System.out.println("缓存统计: " + cache.stats());
}
}
手动实现过期清理
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
public class ManualCleanupExample {
private final ConcurrentHashMap<String, ExpiringValue> map = new ConcurrentHashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
static class ExpiringValue {
final String value;
final long expirationTime;
ExpiringValue(String value, long expirationTime) {
this.value = value;
this.expirationTime = expirationTime;
}
}
public void put(String key, String value, long duration, TimeUnit unit) {
long expirationTime = System.currentTimeMillis() + unit.toMillis(duration);
lock.writeLock().lock();
try {
map.put(key, new ExpiringValue(value, expirationTime));
} finally {
lock.writeLock().unlock();
}
}
public String get(String key) {
lock.readLock().lock();
try {
ExpiringValue ev = map.get(key);
if (ev == null) return null;
// 检查是否过期
if (System.currentTimeMillis() > ev.expirationTime) {
// 惰性删除
lock.readLock().unlock();
lock.writeLock().lock();
try {
map.remove(key);
return null;
} finally {
lock.writeLock().unlock();
lock.readLock().lock();
}
}
return ev.value;
} finally {
lock.readLock().unlock();
}
}
// 主动清理过期数据
public void cleanExpired() {
lock.writeLock().lock();
try {
long now = System.currentTimeMillis();
map.entrySet().removeIf(entry -> now > entry.getValue().expirationTime);
} finally {
lock.writeLock().unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ManualCleanupExample cache = new ManualCleanupExample();
// 添加数据
cache.put("key1", "value1", 2, TimeUnit.SECONDS);
cache.put("key2", "value2", 4, TimeUnit.SECONDS);
// 模拟查询和清理
for (int i = 0; i < 6; i++) {
System.out.println("第" + (i+1) + "秒: ");
System.out.println(" key1: " + cache.get("key1"));
System.out.println(" key2: " + cache.get("key2"));
if (i == 3) {
System.out.println("手动清理过期数据...");
cache.cleanExpired();
}
Thread.sleep(1000);
}
}
}
数据库层面处理
import java.sql.*;
import java.util.concurrent.*;
public class DatabaseExpiredCleanup {
// 定时清理数据库过期数据
public void scheduleDbCleanup() {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
String sql = "DELETE FROM cache_table WHERE expire_time < NOW()";
try (Connection conn = getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
int deleted = stmt.executeUpdate();
if (deleted > 0) {
System.out.println("清理了 " + deleted + " 条过期数据");
}
} catch (SQLException e) {
e.printStackTrace();
}
}, 0, 1, TimeUnit.HOURS); // 每小时执行一次
}
private Connection getConnection() throws SQLException {
// 获取数据库连接
return DriverManager.getConnection("jdbc:mysql://localhost:3306/test",
"user", "password");
}
}
清理策略对比
| 策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 定时扫描 | 实现简单、内存友好 | 清理不及时、定时扫描影响性能 | 对实时性要求不高的场景 |
| 惰性删除 | 无额外开销、按需清理 | 过期数据可能长期占用内存 | 对内存不敏感的应用 |
| 缓存框架 | 功能完善、性能优化 | 引入外部依赖 | 企业级应用 |
| 数据库级清理 | 持久化、可靠性高 | 数据库压力、时效性差 | 数据持久化场景 |
最佳实践建议
-
选择合适的清理策略
- 数据量大:推荐使用缓存框架(Guava/Caffeine)
- 对实时性要求高:惰性删除 + 定时扫描结合
- 数据需要持久化:数据库 + 定时任务
-
注意ConcurrentModificationException
// 错误方式 for (Entry entry : map.entrySet()) { if (expired) map.remove(entry.getKey()); } // 正确方式 map.entrySet().removeIf(entry -> expired(entry)); -
考虑使用WeakHashMap
Map<String, Object> weakMap = new WeakHashMap<>(); // 当key没有被其他强引用持有时,GC会自动回收
-
监控和报警
// 添加被清理数据的日志和监控 removalListener((key, value, cause) -> { log.warn("缓存清理: {} -> {}, 原因: {}", key, value, cause); metrics.counter("cache.eviction").inc(); });
这些方案可以根据你的具体需求选择合适的实现方式,建议优先使用成熟的缓存框架,它们已经处理了各种边界情况和性能优化。