Hutool 的 `TimedCache` 到期会自动清理吗? ——————hutool cache的“惰性清理“和“定期清理“
核心清理机制
1.惰性清理(Lazy Eviction)
TimedCache<String, Object> cache = new TimedCache<>(1000); // 默认过期时间1秒 cache.put("key1", "value1"); // 在get时检查是否过期 Object value = cache.get("key1"); // 如果已过期,返回null并从map中移除每次调用get()或containsKey()时,会检查该键是否过期,如果过期则移除。
2.定期清理(Schedule Prune)
TimedCache<String, Object> cache = new TimedCache<>(1000); cache.schedulePrune(1000); // 启动定时清理,每1000ms执行一次通过调用schedulePrune()方法启动定时清理任务,定期扫描并移除过期项。
回到顶部
🔧 使用示例
示例1:基本使用(不自动清理)
import cn.hutool.cache.impl.TimedCache; public class TimedCacheExample1 { public static void main(String[] args) throws InterruptedException { // 创建缓存,默认过期时间1000ms TimedCache<String, String> cache = new TimedCache<>(1000); // 放入数据 cache.put("key1", "value1"); // 立即获取 System.out.println("立即获取:" + cache.get("key1")); // 输出: value1 // 等待1.1秒 Thread.sleep(1100); // 过期后获取 System.out.println("1.1秒后获取:" + cache.get("key1")); // 输出: null // 注意:此时key1会从map中移除 // 检查大小 System.out.println("缓存大小:" + cache.size()); // 输出: 0 } }示例2:启动定时清理
import cn.hutool.cache.impl.TimedCache; public class TimedCacheExample2 { public static void main(String[] args) throws InterruptedException { // 创建缓存,默认过期时间1000ms TimedCache<String, String> cache = new TimedCache<>(1000); // 启动定时清理,每500ms执行一次 cache.schedulePrune(500); cache.put("key1", "value1"); cache.put("key2", "value2", 2000); // 单独设置过期时间2000ms System.out.println("初始大小:" + cache.size()); // 输出: 2 Thread.sleep(1100); System.out.println("1.1秒后大小:" + cache.size()); // 输出: 1(key1被清理) Thread.sleep(1000); System.out.println("2.1秒后大小:" + cache.size()); // 输出: 0(key2被清理) // 关闭定时清理 cache.cancelPruneSchedule(); } }回到顶部
⚡ 清理时机总结
| 清理类型 | 触发条件 | 清理范围 | 是否立即生效 |
|---|---|---|---|
| 惰性清理 | 调用get()、containsKey()等方法 | 只检查被访问的key | 是 |
| 定时清理 | 定时任务触发(需手动调用schedulePrune()) | 所有过期key | 是 |
| 容量清理 | 达到缓存容量上限 | 最近最少使用的过期项 | 是 |
回到顶部
📊 源码解析
查看TimedCache源码的关键方法:
1.检查过期并移除的方法
// 在get操作时会调用 public V get(K key, boolean isUpdateLastAccess) { CacheObj<K, V> co = getWithoutLock(key); if (co == null) { missCount.increment(); return null; } // 检查是否过期 if (co.isExpired()) { // 过期则移除 remove(key, true); missCount.increment(); return null; } // ... 返回缓存值 }2.定时清理任务
public void schedulePrune(long delay) { this.pruneTimer.schedule(new TimerTask() { @Override public void run() { pruneCache(); // 执行清理 } }, delay, delay); }回到顶部
💡 使用建议
1.根据场景选择清理策略
// 场景1:低频率访问,使用惰性清理即可 TimedCache<String, Object> cache1 = new TimedCache<>(5000); // 场景2:高频率访问,需要定时清理避免内存泄漏 TimedCache<String, Object> cache2 = new TimedCache<>(5000); cache2.schedulePrune(1000); // 每1秒清理一次 // 场景3:需要严格内存控制 TimedCache<String, Object> cache3 = new TimedCache<>(5000, 1000); // 容量限制1000 cache3.schedulePrune(500);2.合理设置过期时间
// 为不同数据设置不同过期时间 TimedCache<String, Object> cache = new TimedCache<>(); // 默认过期时间5秒 cache.setDefaultTimeout(5000); // 特定key设置特定过期时间 cache.put("session_token", "abc123", 30 * 60 * 1000); // 30分钟 cache.put("captcha", "1234", 5 * 60 * 1000); // 5分钟 cache.put("page_cache", "<html>...</html>", 1000); // 1秒3.监听器支持
TimedCache<String, Object> cache = new TimedCache<>(1000); cache.setListener(new CacheListener<String, Object>() { @Override public void onRemove(String key, Object value) { System.out.println("Key被移除:" + key + ",值:" + value); } });回到顶部
⚠️ 注意事项
- 定时清理需手动启动:
schedulePrune()必须显式调用才会启动定时清理 - 内存泄漏风险:如果不调用
schedulePrune()且从不访问过期key,这些key会一直占用内存 - Timer的内存泄漏:定时清理使用
Timer,如果缓存对象被GC,定时任务不会自动取消 - 性能考虑:定时清理间隔不宜过短,特别是缓存项很多时
回到顶部
