diff --git a/CHANGELOG.md b/CHANGELOG.md index 65ea83b52..4cbe5aa12 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.8.9.M1 (2022-10-09) +# 5.8.9.M1 (2022-10-12) ### 🐣新特性 * 【core 】 DateUtil增加isLastDayOfMonth、getLastDayOfMonth方法(pr#824@Gitee) @@ -17,6 +17,7 @@ * 【http 】 修复Http重定全局设置无效问题(pr#2639@Github) * 【core 】 修复ReUtil.replaceAll替换变量错误问题(pr#2639@Github) * 【core 】 修复FileNameUtil.mainName二级扩展名获取错误问题(issue#2642@Github) +* 【cache 】 修复LRUCache移除事件监听失效问题(issue#2647@Github) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/LRUCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/LRUCache.java index c95b97f2e..f3116ba58 100755 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/LRUCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/LRUCache.java @@ -1,5 +1,6 @@ package cn.hutool.cache.impl; +import cn.hutool.core.lang.mutable.Mutable; import cn.hutool.core.map.FixedLinkedHashMap; import java.util.Iterator; @@ -42,7 +43,13 @@ public class LRUCache extends ReentrantCache { this.timeout = timeout; //链表key按照访问顺序排序,调用get方法后,会将这次访问的元素移至头部 - cacheMap = new FixedLinkedHashMap<>(capacity); + final FixedLinkedHashMap, CacheObj> fixedLinkedHashMap = new FixedLinkedHashMap<>(capacity); + fixedLinkedHashMap.setRemoveListener(entry -> { + if(null != listener){ + listener.onRemove(entry.getKey().get(), entry.getValue().getValue()); + } + }); + cacheMap = fixedLinkedHashMap; } // ---------------------------------------------------------------- prune diff --git a/hutool-cache/src/test/java/cn/hutool/cache/LRUCacheTest.java b/hutool-cache/src/test/java/cn/hutool/cache/LRUCacheTest.java index fdc6ea4be..ef739ac39 100755 --- a/hutool-cache/src/test/java/cn/hutool/cache/LRUCacheTest.java +++ b/hutool-cache/src/test/java/cn/hutool/cache/LRUCacheTest.java @@ -3,11 +3,13 @@ package cn.hutool.cache; import cn.hutool.cache.impl.LRUCache; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; /** * 见:https://github.com/dromara/hutool/issues/1895
@@ -20,7 +22,7 @@ public class LRUCacheTest { @Ignore public void putTest(){ //https://github.com/dromara/hutool/issues/2227 - LRUCache cache = CacheUtil.newLRUCache(100, 10); + final LRUCache cache = CacheUtil.newLRUCache(100, 10); for (int i = 0; i < 10000; i++) { //ThreadUtil.execute(()-> cache.put(RandomUtil.randomString(5), "1243", 10)); ThreadUtil.execute(()-> cache.get(RandomUtil.randomString(5), ()->RandomUtil.randomString(10))); @@ -30,15 +32,15 @@ public class LRUCacheTest { @Test public void readWriteTest() throws InterruptedException { - LRUCache cache = CacheUtil.newLRUCache(10); + final LRUCache cache = CacheUtil.newLRUCache(10); for (int i = 0; i < 10; i++) { cache.put(i, i); } - CountDownLatch countDownLatch = new CountDownLatch(10); + final CountDownLatch countDownLatch = new CountDownLatch(10); // 10个线程分别读0-9 10000次 for (int i = 0; i < 10; i++) { - int finalI = i; + final int finalI = i; new Thread(() -> { for (int j = 0; j < 10000; j++) { cache.get(finalI); @@ -49,7 +51,7 @@ public class LRUCacheTest { // 等待读线程结束 countDownLatch.await(); // 按顺序读0-9 - StringBuilder sb1 = new StringBuilder(); + final StringBuilder sb1 = new StringBuilder(); for (int i = 0; i < 10; i++) { sb1.append(cache.get(i)); } @@ -58,10 +60,29 @@ public class LRUCacheTest { // 新加11,此时0最久未使用,应该淘汰0 cache.put(11, 11); - StringBuilder sb2 = new StringBuilder(); + final StringBuilder sb2 = new StringBuilder(); for (int i = 0; i < 10; i++) { sb2.append(cache.get(i)); } Assert.assertEquals("null123456789", sb2.toString()); } + + @Test + public void issue2647Test(){ + final AtomicInteger removeCount = new AtomicInteger(); + + final LRUCache cache = CacheUtil.newLRUCache(3,1); + cache.setListener((key, value) -> { + // 共移除7次 + removeCount.incrementAndGet(); + //Console.log("Start remove k-v, key:{}, value:{}", key, value); + }); + + for (int i = 0; i < 10; i++) { + cache.put(StrUtil.format("key-{}", i), i); + } + + Assert.assertEquals(7, removeCount.get()); + Assert.assertEquals(3, cache.size()); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/FixedLinkedHashMap.java b/hutool-core/src/main/java/cn/hutool/core/map/FixedLinkedHashMap.java index 5ddaaa6f9..0439c41e5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/FixedLinkedHashMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/FixedLinkedHashMap.java @@ -1,21 +1,28 @@ package cn.hutool.core.map; import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Consumer; /** * 固定大小的{@link LinkedHashMap} 实现
* 注意此类非线程安全,由于{@link #get(Object)}操作会修改链表的顺序结构,因此也不可以使用读写锁。 * - * @author looly - * * @param 键类型 * @param 值类型 + * @author looly */ public class FixedLinkedHashMap extends LinkedHashMap { private static final long serialVersionUID = -629171177321416095L; - /** 容量,超过此容量自动删除末尾元素 */ + /** + * 容量,超过此容量自动删除末尾元素 + */ private int capacity; + /** + * 移除监听 + */ + private Consumer> removeListener; /** * 构造 @@ -45,10 +52,26 @@ public class FixedLinkedHashMap extends LinkedHashMap { this.capacity = capacity; } + /** + * 设置自定义移除监听 + * + * @param removeListener 移除监听 + */ + public void setRemoveListener(final Consumer> removeListener) { + this.removeListener = removeListener; + } + @Override protected boolean removeEldestEntry(java.util.Map.Entry eldest) { //当链表元素大于容量时,移除最老(最久未被使用)的元素 - return size() > this.capacity; + if (size() > this.capacity) { + if (null != removeListener) { + // 自定义监听 + removeListener.accept(eldest); + } + return true; + } + return false; } }