From 699508bed453b4cbac35f4b2cb20171be33ccde8 Mon Sep 17 00:00:00 2001 From: nicblusyc <15876923+nicblusyc@user.noreply.gitee.com> Date: Sun, 30 Nov 2025 14:06:28 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat=E6=96=B0=E5=A2=9E=E9=9B=86=E5=90=88?= =?UTF-8?q?=E5=88=86=E7=B1=BB=E5=B7=A5=E5=85=B7=E7=B1=BB=EF=BC=8C=E5=AF=B9?= =?UTF-8?q?=E9=9B=86=E5=90=88=E8=BF=9B=E8=A1=8C=E5=88=86=E9=A1=B5=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E8=BF=94=E5=9B=9E=E4=B8=80=E4=B8=AA=E5=88=86=E9=A1=B5?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1=EF=BC=8C=E8=AF=A5=E5=AF=B9=E8=B1=A1=E5=8C=85?= =?UTF-8?q?=E5=90=AB=E5=88=86=E9=A1=B5=E6=95=B0=E6=8D=AE=E5=92=8C=E5=88=86?= =?UTF-8?q?=E9=A1=B5=E4=BF=A1=E6=81=AF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../v7/core/collection/CollectionPager.java | 120 ++++++++++++++++++ .../core/collection/CollectionPagerTest.java | 20 +++ 2 files changed, 140 insertions(+) create mode 100644 hutool-core/src/main/java/cn/hutool/v7/core/collection/CollectionPager.java create mode 100644 hutool-core/src/test/java/cn/hutool/v7/core/collection/CollectionPagerTest.java diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/collection/CollectionPager.java b/hutool-core/src/main/java/cn/hutool/v7/core/collection/CollectionPager.java new file mode 100644 index 0000000000..4debd3a205 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/v7/core/collection/CollectionPager.java @@ -0,0 +1,120 @@ +package cn.hutool.v7.core.collection; +import java.util.Collections; +import java.util.List; +import java.util.ArrayList; +import java.util.Collection; + +/** + * 集合分页工具类 + * + *

用于对内存中的集合进行分页,并返回分页信息和数据。

+ * + * @author Nic + * @since 2025-11-30 + */ +public class CollectionPager { + + /** + * 分页结果类 + * + * @param 数据类型 + */ + public static class Page { + private final int pageNum; // 当前页码 + private final int pageSize; // 每页大小 + private final int totalPage; // 总页数 + private final int totalCount; // 总记录数 + private final List data; // 当前页数据 + + public Page(int pageNum, int pageSize, int totalCount, List data) { + this.pageNum = pageNum; + this.pageSize = pageSize; + this.totalCount = totalCount; + this.totalPage = (int) Math.ceil((double) totalCount / pageSize); + this.data = data; + } + + // Getters + public int getPageNum() { + return pageNum; + } + + public int getPageSize() { + return pageSize; + } + + public int getTotalPage() { + return totalPage; + } + + public int getTotalCount() { + return totalCount; + } + + public List getData() { + return data; + } + + /** + * 是否有上一页 + */ + public boolean hasPrev() { + return pageNum > 1; + } + + /** + * 是否有下一页 + */ + public boolean hasNext() { + return pageNum < totalPage; + } + + /** + * 获取上一页页码 + */ + public int getPrevPage() { + return hasPrev() ? pageNum - 1 : pageNum; + } + + /** + * 获取下一页页码 + */ + public int getNextPage() { + return hasNext() ? pageNum + 1 : pageNum; + } + } + + /** + * 对集合进行分页 + * + * @param 元素类型 + * @param collection 集合 + * @param pageNum 页码,从1开始 + * @param pageSize 每页大小 + * @return 分页结果对象 + */ + public static Page paginate(Collection collection, int pageNum, int pageSize) { + if (collection == null) { + collection = Collections.emptyList(); + } + + if (pageNum < 1) { + pageNum = 1; + } + + if (pageSize < 1) { + pageSize = 10; + } + + List list = new ArrayList<>(collection); + int totalCount = list.size(); + int fromIndex = (pageNum - 1) * pageSize; + if (fromIndex >= totalCount) { + return new Page<>(pageNum, pageSize, totalCount, Collections.emptyList()); + } + + int toIndex = Math.min(fromIndex + pageSize, totalCount); + List pageData = list.subList(fromIndex, toIndex); + return new Page<>(pageNum, pageSize, totalCount, new ArrayList<>(pageData)); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/v7/core/collection/CollectionPagerTest.java b/hutool-core/src/test/java/cn/hutool/v7/core/collection/CollectionPagerTest.java new file mode 100644 index 0000000000..932502fde2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/v7/core/collection/CollectionPagerTest.java @@ -0,0 +1,20 @@ +package cn.hutool.v7.core.collection; + +import org.junit.jupiter.api.Test; +import java.util.*; + +public class CollectionPagerTest { + + @Test + public void testPage() { + List list = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j"); + CollectionPager.Page page = CollectionPager.paginate(list, 2, 3); + + // 获取第2页的数据,每页3条,那么应该返回:["d","e","f"] + System.out.println(page.getData()); // [d, e, f] + System.out.println(page.getPageNum()); // 2 + System.out.println(page.getPageSize()); // 3 + System.out.println(page.getTotalCount()); // 10 + System.out.println(page.getTotalPage()); // 4 + } +} -- Gitee From 17ebbd2c0069a3d82aea458cf59c12e9a542b778 Mon Sep 17 00:00:00 2001 From: nicblusyc <15876923+nicblusyc@user.noreply.gitee.com> Date: Mon, 1 Dec 2025 23:41:17 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat=E7=BC=93=E5=AD=98=E6=89=A9=E5=B1=95?= =?UTF-8?q?=EF=BC=9A=E6=96=B0=E5=A2=9E=E5=A4=9A=E7=BA=A7=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E3=80=81=E5=BC=82=E6=AD=A5=E5=88=B7=E6=96=B0=E3=80=81=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E9=A2=84=E7=83=AD=E7=AD=89=E5=8A=9F=E8=83=BD=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/v7/core/cache/CacheStats.java | 214 +++++++ .../cn/hutool/v7/core/cache/SmartCache.java | 91 +++ .../v7/core/cache/SmartCacheBuilder.java | 122 ++++ .../hutool/v7/core/cache/SmartCacheUtil.java | 116 ++++ .../v7/core/cache/impl/SmartCacheImpl.java | 573 ++++++++++++++++++ .../v7/core/cache/SmartCacheBasicTest.java | 153 +++++ 6 files changed, 1269 insertions(+) create mode 100644 hutool-core/src/main/java/cn/hutool/v7/core/cache/CacheStats.java create mode 100644 hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCache.java create mode 100644 hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCacheBuilder.java create mode 100644 hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCacheUtil.java create mode 100644 hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/SmartCacheImpl.java create mode 100644 hutool-core/src/test/java/cn/hutool/v7/core/cache/SmartCacheBasicTest.java diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/CacheStats.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/CacheStats.java new file mode 100644 index 0000000000..577115e7e9 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/v7/core/cache/CacheStats.java @@ -0,0 +1,214 @@ +package cn.hutool.v7.core.cache; + +import java.io.Serializable; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.LongAdder; + +/** + * 缓存统计信息 + * + * @author Nic + */ +public class CacheStats implements Serializable { + private static final long serialVersionUID = 1L; + + private final LongAdder hitCount; + private final LongAdder missCount; + private final LongAdder loadSuccessCount; + private final LongAdder loadFailureCount; + private final LongAdder evictionCount; + private final LongAdder totalLoadTime; // 纳秒 + private final AtomicLong cacheSize; + private final long startTime; + + /** + * 构建器模式 + */ + public static class Builder { + private final CacheStats stats; + + public Builder() { + this.stats = new CacheStats(); + } + + public Builder hitCount(long hitCount) { + stats.hitCount.add(hitCount); + return this; + } + + public Builder missCount(long missCount) { + stats.missCount.add(missCount); + return this; + } + + public Builder loadSuccessCount(long loadSuccessCount) { + stats.loadSuccessCount.add(loadSuccessCount); + return this; + } + + public Builder loadFailureCount(long loadFailureCount) { + stats.loadFailureCount.add(loadFailureCount); + return this; + } + + public Builder evictionCount(long evictionCount) { + stats.evictionCount.add(evictionCount); + return this; + } + + public Builder totalLoadTime(long totalLoadTime) { + stats.totalLoadTime.add(totalLoadTime); + return this; + } + + public Builder cacheSize(long cacheSize) { + stats.cacheSize.set(cacheSize); + return this; + } + + public CacheStats build() { + return stats; + } + } + + public CacheStats() { + this.hitCount = new LongAdder(); + this.missCount = new LongAdder(); + this.loadSuccessCount = new LongAdder(); + this.loadFailureCount = new LongAdder(); + this.evictionCount = new LongAdder(); + this.totalLoadTime = new LongAdder(); + this.cacheSize = new AtomicLong(0); + this.startTime = System.currentTimeMillis(); + } + + // ========== 统计计算方法 ========== + + /** + * 获取命中率 + */ + public double getHitRate() { + long requestCount = hitCount.longValue() + missCount.longValue(); + return requestCount == 0 ? 1.0 : (double) hitCount.longValue() / requestCount; + } + + /** + * 获取未命中率 + */ + public double getMissRate() { + long requestCount = hitCount.longValue() + missCount.longValue(); + return requestCount == 0 ? 0.0 : (double) missCount.longValue() / requestCount; + } + + /** + * 获取平均加载时间(毫秒) + */ + public double getAverageLoadTime() { + long total = totalLoadTime.longValue(); + long success = loadSuccessCount.longValue(); + return success == 0 ? 0.0 : (total / 1_000_000.0) / success; + } + + /** + * 获取加载失败率 + */ + public double getLoadFailureRate() { + long totalLoads = loadSuccessCount.longValue() + loadFailureCount.longValue(); + return totalLoads == 0 ? 0.0 : (double) loadFailureCount.longValue() / totalLoads; + } + + /** + * 获取缓存运行时间(秒) + */ + public long getRuntimeSeconds() { + return (System.currentTimeMillis() - startTime) / 1000; + } + + // ========== Getter方法 ========== + + public long getHitCount() { + return hitCount.longValue(); + } + + public long getMissCount() { + return missCount.longValue(); + } + + public long getLoadSuccessCount() { + return loadSuccessCount.longValue(); + } + + public long getLoadFailureCount() { + return loadFailureCount.longValue(); + } + + public long getEvictionCount() { + return evictionCount.longValue(); + } + + public long getTotalLoadTime() { + return totalLoadTime.longValue(); + } + + public long getCacheSize() { + return cacheSize.get(); + } + + public long getStartTime() { + return startTime; + } + + /** + * 记录一次命中 + */ + public void recordHit() { + hitCount.increment(); + } + + /** + * 记录一次未命中 + */ + public void recordMiss() { + missCount.increment(); + } + + /** + * 记录一次成功的加载 + */ + public void recordLoadSuccess(long loadTime) { + loadSuccessCount.increment(); + totalLoadTime.add(loadTime); + } + + /** + * 记录一次失败的加载 + */ + public void recordLoadFailure() { + loadFailureCount.increment(); + } + + /** + * 记录一次驱逐 + */ + public void recordEviction() { + evictionCount.increment(); + } + + /** + * 更新缓存大小 + */ + public void setCacheSize(long size) { + cacheSize.set(size); + } + + @Override + public String toString() { + return String.format( + "CacheStats{hitRate=%.2f%%, hits=%d, misses=%d, loadSuccess=%d, loadFailure=%d, " + + "evictions=%d, avgLoadTime=%.2fms, size=%d, runtime=%ds}", + getHitRate() * 100, getHitCount(), getMissCount(), getLoadSuccessCount(), + getLoadFailureCount(), getEvictionCount(), getAverageLoadTime(), + getCacheSize(), getRuntimeSeconds() + ); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCache.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCache.java new file mode 100644 index 0000000000..2f11dc8057 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCache.java @@ -0,0 +1,91 @@ +package cn.hutool.v7.core.cache; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +/** + * 智能缓存接口 - 扩展Hutool标准缓存功能 + * + *

提供多级缓存、异步刷新、缓存预热等高级功能

+ * + * @author Nic + */ +public interface SmartCache extends Cache { + + /** + * 批量获取缓存项 + * + * @param keys 键集合 + * @return 键值对映射 + */ + Map getAll(Collection keys); + + /** + * 批量放入缓存项 + * + * @param map 键值对映射 + */ + void putAll(Map map); + + /** + * 异步刷新缓存项 + * + * @param key 缓存键 + * @return CompletableFuture包装的缓存值 + */ + CompletableFuture refreshAsync(K key); + + /** + * 缓存预热 + * + * @param keys 需要预热的键集合 + * @return 预热成功的数量 + */ + int warmUp(Collection keys); + + /** + * 原子操作:如果不存在则计算并放入 + * + * @param key 缓存键 + * @param mappingFunction 映射函数 + * @return 缓存值 + */ + V computeIfAbsent(K key, Function mappingFunction); + + /** + * 原子操作:如果存在则重新计算 + * + * @param key 缓存键 + * @param remappingFunction 重新映射函数 + * @return 新的缓存值 + */ + V computeIfPresent(K key, Function remappingFunction); + + /** + * 获取缓存统计信息 + * + * @return 缓存统计 + */ + CacheStats getStats(); + + /** + * 清除所有统计信息 + */ + void clearStats(); + + /** + * 获取缓存名称 + * + * @return 缓存名称 + */ + String getName(); + + /** + * 设置缓存名称 + * + * @param name 缓存名称 + */ + void setName(String name); +} diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCacheBuilder.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCacheBuilder.java new file mode 100644 index 0000000000..9313dbcdae --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCacheBuilder.java @@ -0,0 +1,122 @@ +package cn.hutool.v7.core.cache; + +import cn.hutool.v7.core.cache.impl.SmartCacheImpl; +import cn.hutool.v7.core.text.StrUtil; + +import java.time.Duration; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Function; + +/** + * 智能缓存构建器 + * + * @author Nic + */ +public class SmartCacheBuilder { + + // 必需参数 + private final Cache cache; + + // 可选参数 + private String name = "SmartCache"; + private boolean enableStats = true; + private boolean enableAsyncRefresh = false; + private int warmUpBatchSize = 100; + private Duration refreshTimeout = Duration.ofSeconds(30); + private ExecutorService refreshExecutor; + private Function cacheLoader; + + /** + * 私有构造器 + */ + private SmartCacheBuilder(Cache cache) { + this.cache = cache; + } + + /** + * 创建构建器 + */ + public static SmartCacheBuilder of(Cache cache) { + return new SmartCacheBuilder<>(cache); + } + + /** + * 设置缓存名称 + */ + public SmartCacheBuilder name(String name) { + this.name = StrUtil.defaultIfBlank(name, "SmartCache"); + return this; + } + + /** + * 启用统计 + */ + public SmartCacheBuilder enableStats(boolean enableStats) { + this.enableStats = enableStats; + return this; + } + + /** + * 启用异步刷新 + */ + public SmartCacheBuilder enableAsyncRefresh(boolean enableAsyncRefresh) { + this.enableAsyncRefresh = enableAsyncRefresh; + return this; + } + + /** + * 设置预热批次大小 + */ + public SmartCacheBuilder warmUpBatchSize(int warmUpBatchSize) { + this.warmUpBatchSize = Math.max(1, warmUpBatchSize); + return this; + } + + /** + * 设置刷新超时时间 + */ + public SmartCacheBuilder refreshTimeout(Duration refreshTimeout) { + this.refreshTimeout = refreshTimeout; + return this; + } + + /** + * 设置刷新线程池 + */ + public SmartCacheBuilder refreshExecutor(ExecutorService refreshExecutor) { + this.refreshExecutor = refreshExecutor; + return this; + } + + /** + * 设置缓存加载器 + */ + public SmartCacheBuilder cacheLoader(Function cacheLoader) { + this.cacheLoader = cacheLoader; + return this; + } + + /** + * 构建智能缓存 + */ + public SmartCache build() { + // 确保有刷新线程池(如果需要异步刷新) + if (enableAsyncRefresh && refreshExecutor == null) { + refreshExecutor = Executors.newFixedThreadPool( + Math.max(2, Runtime.getRuntime().availableProcessors() / 2) + ); + } + + return new SmartCacheImpl<>( + cache, + name, + enableStats, + enableAsyncRefresh, + warmUpBatchSize, + refreshTimeout, + refreshExecutor, + cacheLoader + ); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCacheUtil.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCacheUtil.java new file mode 100644 index 0000000000..f4e439ff3e --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCacheUtil.java @@ -0,0 +1,116 @@ +package cn.hutool.v7.core.cache; + +import cn.hutool.v7.core.cache.impl.LRUCache; + +/** + * 智能缓存工具类 + * + * @author Nic + */ +public class SmartCacheUtil { + + private SmartCacheUtil() { + // 工具类,禁止实例化 + } + + /** + * 创建LRU智能缓存 + */ + public static SmartCache newLRUSmartCache(int capacity) { + return (SmartCache) SmartCacheBuilder.of(CacheUtil.newLRUCache(capacity)) + .name("LRU-SmartCache") + .build(); + } + + /** + * 创建LFU智能缓存 + */ + public static SmartCache newLFUSmartCache(int capacity) { + return (SmartCache) SmartCacheBuilder.of(CacheUtil.newLFUCache(capacity)) + .name("LFU-SmartCache") + .build(); + } + + /** + * 创建FIFO智能缓存 + */ + public static SmartCache newFIFOSmartCache(int capacity) { + return (SmartCache) SmartCacheBuilder.of(CacheUtil.newFIFOCache(capacity)) + .name("FIFO-SmartCache") + .build(); + } + + /** + * 创建带加载器的智能缓存 + */ + public static SmartCache newSmartCache( + Cache cache, + java.util.function.Function loader) { + + return SmartCacheBuilder.of(cache) + .cacheLoader(loader) + .enableAsyncRefresh(true) + .enableStats(true) + .build(); + } + + /** + * 创建定时过期的智能缓存 + */ + public static SmartCache newTimedSmartCache( + int capacity, + long timeout, + java.util.function.Function loader) { + + Cache cache = new LRUCache<>(capacity, timeout) { + @Override + public boolean isFull() { + return this.cacheMap.size() >= capacity; + } + }; + + return SmartCacheBuilder.of(cache) + .name("Timed-SmartCache") + .cacheLoader(loader) + .enableStats(true) + .build(); + } + + /** + * 获取缓存的详细统计信息 + */ + public static String getDetailedStats(SmartCache cache) { + if (cache == null) { + return "Cache is null"; + } + + try { + CacheStats stats = cache.getStats(); + return String.format( + "Cache: %s\n" + + " Size: %d / %d\n" + + " Hit Rate: %.2f%%\n" + + " Hits: %d\n" + + " Misses: %d\n" + + " Load Success: %d\n" + + " Load Failure: %d\n" + + " Avg Load Time: %.2fms\n" + + " Evictions: %d\n" + + " Runtime: %ds", + cache.getName(), + cache.size(), + cache.capacity(), + stats.getHitRate() * 100, + stats.getHitCount(), + stats.getMissCount(), + stats.getLoadSuccessCount(), + stats.getLoadFailureCount(), + stats.getAverageLoadTime(), + stats.getEvictionCount(), + stats.getRuntimeSeconds() + ); + } catch (Exception e) { + return "Unable to get stats: " + e.getMessage(); + } + } +} diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/SmartCacheImpl.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/SmartCacheImpl.java new file mode 100644 index 0000000000..162de67573 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/SmartCacheImpl.java @@ -0,0 +1,573 @@ +package cn.hutool.v7.core.cache.impl; + +import cn.hutool.v7.core.cache.Cache; +import cn.hutool.v7.core.cache.CacheStats; +import cn.hutool.v7.core.cache.SmartCache; +import cn.hutool.v7.core.collection.CollUtil; +import cn.hutool.v7.core.collection.iter.CopiedIter; +import cn.hutool.v7.core.collection.partition.Partition; +import cn.hutool.v7.core.func.SerSupplier; +import cn.hutool.v7.core.map.MapUtil; +import cn.hutool.v7.core.text.StrUtil; +import java.time.Duration; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; + +/** + * 智能缓存实现 + * + * @author Nic + */ +public class SmartCacheImpl implements SmartCache { + + // 底层缓存 + private final Cache delegate; + + // 配置参数 + private String name; + private final boolean enableStats; + private final boolean enableAsyncRefresh; + private final int warmUpBatchSize; + private final Duration refreshTimeout; + private final ExecutorService refreshExecutor; + private final Function cacheLoader; + + // 统计信息 + private final CacheStats stats; + + // 锁机制 + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private final Map> pendingRefreshes = new ConcurrentHashMap<>(); + + /** + * 构造器 + */ + public SmartCacheImpl( + Cache delegate, + String name, + boolean enableStats, + boolean enableAsyncRefresh, + int warmUpBatchSize, + Duration refreshTimeout, + ExecutorService refreshExecutor, + Function cacheLoader) { + + this.delegate = delegate; + this.name = name; + this.enableStats = enableStats; + this.enableAsyncRefresh = enableAsyncRefresh; + this.warmUpBatchSize = Math.max(1, warmUpBatchSize); + this.refreshTimeout = refreshTimeout != null ? refreshTimeout : Duration.ofSeconds(30); + this.refreshExecutor = refreshExecutor; + this.cacheLoader = cacheLoader; + this.stats = enableStats ? new CacheStats() : null; + } + + // ========== 实现Cache接口方法 ========== + + @Override + public void put(K key, V object, long timeout) { + lock.writeLock().lock(); + try { + delegate.put(key, object, timeout); + if (enableStats) { + stats.setCacheSize(delegate.size()); + } + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public void put(K key, V object) { + put(key, object, 0); + } + + @Override + public V get(K key, boolean isUpdateLastAccess) { + lock.readLock().lock(); + try { + V value = delegate.get(key, isUpdateLastAccess); + + if (enableStats) { + if (value != null) { + stats.recordHit(); + } else { + stats.recordMiss(); + + // 如果有缓存加载器,尝试加载 + if (cacheLoader != null) { + long startTime = System.nanoTime(); + try { + value = cacheLoader.apply(key); + if (value != null) { + delegate.put(key, value); + stats.recordLoadSuccess(System.nanoTime() - startTime); + } + } catch (Exception e) { + stats.recordLoadFailure(); + throw new CacheException("Failed to load cache value for key: " + key, e); + } + } + } + } + + return value; + } finally { + lock.readLock().unlock(); + } + } + + @Override + public V get(K key) { + return get(key, false); + } + + @Override + public Iterator iterator() { + return delegate.iterator(); + } + + @Override + public int prune() { + lock.writeLock().lock(); + try { + int pruned = delegate.prune(); + if (enableStats && pruned > 0) { + for (int i = 0; i < pruned; i++) { + stats.recordEviction(); + } + stats.setCacheSize(delegate.size()); + } + return pruned; + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public boolean isFull() { + lock.readLock().lock(); + try { + return delegate.isFull(); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public void remove(K key) { + lock.writeLock().lock(); + try { + delegate.remove(key); + if (enableStats) { + stats.setCacheSize(delegate.size()); + } + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public void clear() { + lock.writeLock().lock(); + try { + delegate.clear(); + if (enableStats) { + stats.setCacheSize(0); + } + pendingRefreshes.clear(); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public int capacity() { + lock.readLock().lock(); + try { + return delegate.capacity(); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public long timeout() { + lock.readLock().lock(); + try { + return delegate.timeout(); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public boolean isEmpty() { + lock.readLock().lock(); + try { + return delegate.isEmpty(); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public int size() { + lock.readLock().lock(); + try { + return delegate.size(); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public boolean containsKey(K key) { + lock.readLock().lock(); + try { + return delegate.containsKey(key); + } finally { + lock.readLock().unlock(); + } + } + + // ========== 实现SmartCache接口方法 ========== + + @Override + public Map getAll(Collection keys) { + if (CollUtil.isEmpty(keys)) { + return Collections.emptyMap(); + } + + lock.readLock().lock(); + try { + Map result = new HashMap<>(keys.size()); + + for (K key : keys) { + V value = get(key); + if (value != null) { + result.put(key, value); + } + } + + return result; + } finally { + lock.readLock().unlock(); + } + } + + @Override + public void putAll(Map map) { + if (MapUtil.isEmpty(map)) { + return; + } + + lock.writeLock().lock(); + try { + for (Map.Entry entry : map.entrySet()) { + delegate.put(entry.getKey(), entry.getValue()); + } + + if (enableStats) { + stats.setCacheSize(delegate.size()); + } + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public CompletableFuture refreshAsync(K key) { + if (!enableAsyncRefresh) { + throw new UnsupportedOperationException("Async refresh is not enabled"); + } + + if (cacheLoader == null) { + throw new IllegalStateException("Cache loader is required for async refresh"); + } + + // 检查是否已经有正在进行的刷新 + CompletableFuture pending = pendingRefreshes.get(key); + if (pending != null) { + return pending; + } + + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + long startTime = System.nanoTime(); + V newValue = cacheLoader.apply(key); + + if (newValue != null) { + lock.writeLock().lock(); + try { + delegate.put(key, newValue); + if (enableStats) { + stats.recordLoadSuccess(System.nanoTime() - startTime); + } + } finally { + lock.writeLock().unlock(); + } + } + + return newValue; + } catch (Exception e) { + if (enableStats) { + stats.recordLoadFailure(); + } + throw new CompletionException(e); + } finally { + pendingRefreshes.remove(key); + } + }, refreshExecutor); + + // 设置超时 + future = future.orTimeout(refreshTimeout.toMillis(), TimeUnit.MILLISECONDS) + .exceptionally(ex -> { + pendingRefreshes.remove(key); + return null; + }); + + pendingRefreshes.put(key, future); + return future; + } + + @Override + public int warmUp(Collection keys) { + if (cacheLoader == null || CollUtil.isEmpty(keys)) { + return 0; + } + + int warmedUp = 0; + Collection> batches = new Partition<>(new ArrayList<>(keys), warmUpBatchSize); + + for (List batch : batches) { + lock.writeLock().lock(); + try { + for (K key : batch) { + if (!delegate.containsKey(key)) { + try { + V value = cacheLoader.apply(key); + if (value != null) { + delegate.put(key, value); + warmedUp++; + } + } catch (Exception e) { + // 忽略单个键的加载失败,继续处理其他键 + } + } + } + } finally { + lock.writeLock().unlock(); + } + } + + if (enableStats) { + stats.setCacheSize(delegate.size()); + } + + return warmedUp; + } + + @Override + public V computeIfAbsent(K key, Function mappingFunction) { + lock.writeLock().lock(); + try { + V value = delegate.get(key); + if (value == null && mappingFunction != null) { + long startTime = System.nanoTime(); + try { + value = mappingFunction.apply(key); + if (value != null) { + delegate.put(key, value); + + if (enableStats) { + stats.recordLoadSuccess(System.nanoTime() - startTime); + stats.setCacheSize(delegate.size()); + } + } + } catch (Exception e) { + if (enableStats) { + stats.recordLoadFailure(); + } + throw new CacheException("Failed to compute value for key: " + key, e); + } + } + + return value; + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public V computeIfPresent(K key, Function remappingFunction) { + lock.writeLock().lock(); + try { + if (delegate.containsKey(key) && remappingFunction != null) { + long startTime = System.nanoTime(); + try { + V newValue = remappingFunction.apply(key); + if (newValue != null) { + delegate.put(key, newValue); + + if (enableStats) { + stats.recordLoadSuccess(System.nanoTime() - startTime); + } + } + return newValue; + } catch (Exception e) { + if (enableStats) { + stats.recordLoadFailure(); + } + throw new CacheException("Failed to compute value for key: " + key, e); + } + } + return null; + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public CacheStats getStats() { + if (!enableStats) { + throw new UnsupportedOperationException("Statistics are not enabled"); + } + + lock.readLock().lock(); + try { + stats.setCacheSize(delegate.size()); + return stats; + } finally { + lock.readLock().unlock(); + } + } + + @Override + public void clearStats() { + if (!enableStats) { + throw new UnsupportedOperationException("Statistics are not enabled"); + } + + lock.writeLock().lock(); + try { + // 创建新的统计实例,保留缓存大小 + long currentSize = stats.getCacheSize(); + stats.setCacheSize(currentSize); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = StrUtil.defaultIfBlank(name, "SmartCache"); + } + + /** + * 获取底层缓存 + */ + public Cache getDelegate() { + return delegate; + } + + @Override + public V get(K key, boolean isUpdateLastAccess, long timeout, SerSupplier valueFactory) { + if (key == null) { + throw new NullPointerException("Key must not be null"); + } + + lock.readLock().lock(); + V value = null; + try { + // 1. 优先尝试从底层缓存获取 + value = delegate.get(key, isUpdateLastAccess); + } finally { + lock.readLock().unlock(); + } + + // 2. 如果缓存未命中,则使用工厂方法创建、缓存并返回新值 + if (value == null && valueFactory != null) { + lock.writeLock().lock(); + try { + // 双重检查锁定模式,防止在获取写锁期间,其他线程已经创建并插入了值 + value = delegate.get(key, isUpdateLastAccess); + if (value == null) { + // 记录加载开始时间,用于统计 + long loadStartTime = System.nanoTime(); + try { + // 调用工厂方法创建新值 + value = valueFactory.get(); + // 如果工厂成功创建了值,则将其放入缓存 + if (value != null) { + if (timeout > 0) { + // 使用传入的自定义超时时间 + delegate.put(key, value, timeout); + } else { + // 使用缓存的默认超时策略 + delegate.put(key, value); + } + + // 记录加载成功(如果开启了统计) + if (enableStats) { + stats.recordLoadSuccess(System.nanoTime() - loadStartTime); + } + } else { + // 工厂方法返回了null,记录加载失败(可选逻辑) + if (enableStats) { + stats.recordLoadFailure(); + } + // 注意:此时并未将null值存入缓存,下次请求仍会触发加载 + } + } catch (Exception e) { + if (enableStats) { + stats.recordLoadFailure(); + } + // 可以根据需要决定是抛出异常,还是返回null。 + // 为了保持接口的健壮性,这里将异常包装后抛出。 + throw new CacheException("Failed to load value for key: " + key, e); + } + } + // 无论新值是否由当前线程创建,写锁块结束时,value变量中已经有了最终结果。 + } finally { + lock.writeLock().unlock(); + } + } + // 返回最终结果 + return value; + } + + + @Override + public Iterator> cacheObjIterator() { + CopiedIter> copiedIterator; + lock.readLock().lock(); + try { + copiedIterator = CopiedIter.copyOf(this.delegate.cacheObjIterator()); + } finally { + lock.readLock().unlock(); + } + return new CacheObjIterator<>(copiedIterator); + } + + /** + * 自定义缓存异常 + */ + public static class CacheException extends RuntimeException { + public CacheException(String message) { + super(message); + } + + public CacheException(String message, Throwable cause) { + super(message, cause); + } + } +} diff --git a/hutool-core/src/test/java/cn/hutool/v7/core/cache/SmartCacheBasicTest.java b/hutool-core/src/test/java/cn/hutool/v7/core/cache/SmartCacheBasicTest.java new file mode 100644 index 0000000000..8ce7abf432 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/v7/core/cache/SmartCacheBasicTest.java @@ -0,0 +1,153 @@ +package cn.hutool.v7.core.cache; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import static org.junit.jupiter.api.Assertions.*; + +/** + * 智能缓存基础功能测试 + */ +@DisplayName("智能缓存基础功能测试") +public class SmartCacheBasicTest { + + private SmartCache cache; + private AtomicInteger loadCounter; + + @BeforeEach + void setUp() { + loadCounter = new AtomicInteger(0); + + cache = SmartCacheBuilder.of(CacheUtil.newLRUCache(10)) + .name("TestCache") + .enableStats(true) + .cacheLoader(key -> { + loadCounter.incrementAndGet(); + // 模拟加载耗时 + try { + Thread.sleep(10); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return "value_" + key; + }) + .build(); + } + + @Test + @DisplayName("测试基本put和get操作") + void testBasicPutAndGet() { + cache.put("key1", "value1"); + + assertEquals("value1", cache.get("key1")); + } + + @Test + @DisplayName("测试缓存加载器") + void testCacheLoader() { + // 第一次获取,应该触发加载 + assertEquals("value_key2", cache.get("key2")); + assertEquals(1, loadCounter.get()); + + // 第二次获取,应该使用缓存,不会触发加载 + assertEquals("value_key2", cache.get("key2")); + assertEquals(1, loadCounter.get()); // 计数器不变 + + // 获取另一个键,应该再次触发加载 + assertEquals("value_key3", cache.get("key3")); + assertEquals(2, loadCounter.get()); + } + + @Test + @DisplayName("测试批量操作") + void testBatchOperations() { + // 批量放入 + Map data = new HashMap<>(); + data.put("batch1", "value1"); + data.put("batch2", "value2"); + data.put("batch3", "value3"); + + cache.putAll(data); + + // 批量获取 + Map result = cache.getAll(Arrays.asList("batch1", "batch2", "batch3", "non_existent")); + + assertEquals(4, result.size()); + assertEquals("value1", result.get("batch1")); + assertEquals("value2", result.get("batch2")); + assertEquals("value3", result.get("batch3")); + assertTrue(result.containsKey("non_existent")); + } + + @Test + @DisplayName("测试computeIfAbsent") + void testComputeIfAbsent() { + AtomicInteger computeCounter = new AtomicInteger(0); + + // 第一次计算 + String result1 = (String) cache.computeIfAbsent("compute1", key -> { + computeCounter.incrementAndGet(); + return "computed_" + key; + }); + + assertEquals("computed_compute1", result1); + assertEquals(1, computeCounter.get()); + + // 第二次获取,应该使用缓存 + String result2 = (String) cache.computeIfAbsent("compute1", key -> { + computeCounter.incrementAndGet(); + return "should_not_be_called"; + }); + + assertEquals("computed_compute1", result2); + assertEquals(1, computeCounter.get()); // 计数器不变 + + // 测试不存在的键 + assertNull(cache.computeIfAbsent("nullKey", key -> null)); + } + + @Test + @DisplayName("测试缓存预热") + void testWarmUp() { + // 清除初始状态 + cache.clear(); + + // 预热 + int warmed = cache.warmUp(Arrays.asList("warm1", "warm2", "warm3")); + + assertEquals(3, warmed); + assertEquals(3, cache.size()); + + // 验证预热的内容 + assertEquals("value_warm1", cache.get("warm1")); + assertEquals("value_warm2", cache.get("warm2")); + assertEquals("value_warm3", cache.get("warm3")); + + // 预热已存在的键,应该不会重复加载 + int alreadyWarmed = cache.warmUp(Arrays.asList("warm1", "warm4")); + assertEquals(1, alreadyWarmed); // 只有warm4是新加载的 + } + + @Test + @DisplayName("测试缓存容量和大小") + void testCapacityAndSize() { + SmartCache smallCache = SmartCacheUtil.newLRUSmartCache(3); + + smallCache.put("1", "a"); + smallCache.put("2", "b"); + smallCache.put("3", "c"); + + assertEquals(3, smallCache.size()); + assertEquals(3, smallCache.capacity()); + + // 超过容量,应该触发淘汰 + smallCache.put("4", "d"); + + // 由于是LRU,第一个元素可能被淘汰 + assertTrue(smallCache.size() <= 3); + } +} -- Gitee From 21ad4abb1d8454211313b94ebbaa13152ab9dde2 Mon Sep 17 00:00:00 2001 From: nicblusyc <15876923+nicblusyc@user.noreply.gitee.com> Date: Mon, 1 Dec 2025 23:44:51 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../v7/core/collection/CollectionPager.java | 120 ------------------ .../core/collection/CollectionPagerTest.java | 20 --- 2 files changed, 140 deletions(-) delete mode 100644 hutool-core/src/main/java/cn/hutool/v7/core/collection/CollectionPager.java delete mode 100644 hutool-core/src/test/java/cn/hutool/v7/core/collection/CollectionPagerTest.java diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/collection/CollectionPager.java b/hutool-core/src/main/java/cn/hutool/v7/core/collection/CollectionPager.java deleted file mode 100644 index 4debd3a205..0000000000 --- a/hutool-core/src/main/java/cn/hutool/v7/core/collection/CollectionPager.java +++ /dev/null @@ -1,120 +0,0 @@ -package cn.hutool.v7.core.collection; -import java.util.Collections; -import java.util.List; -import java.util.ArrayList; -import java.util.Collection; - -/** - * 集合分页工具类 - * - *

用于对内存中的集合进行分页,并返回分页信息和数据。

- * - * @author Nic - * @since 2025-11-30 - */ -public class CollectionPager { - - /** - * 分页结果类 - * - * @param 数据类型 - */ - public static class Page { - private final int pageNum; // 当前页码 - private final int pageSize; // 每页大小 - private final int totalPage; // 总页数 - private final int totalCount; // 总记录数 - private final List data; // 当前页数据 - - public Page(int pageNum, int pageSize, int totalCount, List data) { - this.pageNum = pageNum; - this.pageSize = pageSize; - this.totalCount = totalCount; - this.totalPage = (int) Math.ceil((double) totalCount / pageSize); - this.data = data; - } - - // Getters - public int getPageNum() { - return pageNum; - } - - public int getPageSize() { - return pageSize; - } - - public int getTotalPage() { - return totalPage; - } - - public int getTotalCount() { - return totalCount; - } - - public List getData() { - return data; - } - - /** - * 是否有上一页 - */ - public boolean hasPrev() { - return pageNum > 1; - } - - /** - * 是否有下一页 - */ - public boolean hasNext() { - return pageNum < totalPage; - } - - /** - * 获取上一页页码 - */ - public int getPrevPage() { - return hasPrev() ? pageNum - 1 : pageNum; - } - - /** - * 获取下一页页码 - */ - public int getNextPage() { - return hasNext() ? pageNum + 1 : pageNum; - } - } - - /** - * 对集合进行分页 - * - * @param 元素类型 - * @param collection 集合 - * @param pageNum 页码,从1开始 - * @param pageSize 每页大小 - * @return 分页结果对象 - */ - public static Page paginate(Collection collection, int pageNum, int pageSize) { - if (collection == null) { - collection = Collections.emptyList(); - } - - if (pageNum < 1) { - pageNum = 1; - } - - if (pageSize < 1) { - pageSize = 10; - } - - List list = new ArrayList<>(collection); - int totalCount = list.size(); - int fromIndex = (pageNum - 1) * pageSize; - if (fromIndex >= totalCount) { - return new Page<>(pageNum, pageSize, totalCount, Collections.emptyList()); - } - - int toIndex = Math.min(fromIndex + pageSize, totalCount); - List pageData = list.subList(fromIndex, toIndex); - return new Page<>(pageNum, pageSize, totalCount, new ArrayList<>(pageData)); - } -} diff --git a/hutool-core/src/test/java/cn/hutool/v7/core/collection/CollectionPagerTest.java b/hutool-core/src/test/java/cn/hutool/v7/core/collection/CollectionPagerTest.java deleted file mode 100644 index 932502fde2..0000000000 --- a/hutool-core/src/test/java/cn/hutool/v7/core/collection/CollectionPagerTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.hutool.v7.core.collection; - -import org.junit.jupiter.api.Test; -import java.util.*; - -public class CollectionPagerTest { - - @Test - public void testPage() { - List list = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j"); - CollectionPager.Page page = CollectionPager.paginate(list, 2, 3); - - // 获取第2页的数据,每页3条,那么应该返回:["d","e","f"] - System.out.println(page.getData()); // [d, e, f] - System.out.println(page.getPageNum()); // 2 - System.out.println(page.getPageSize()); // 3 - System.out.println(page.getTotalCount()); // 10 - System.out.println(page.getTotalPage()); // 4 - } -} -- Gitee