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 extends K, ? extends V> 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 extends K, ? extends V> map) {
+ if (MapUtil.isEmpty(map)) {
+ return;
+ }
+
+ lock.writeLock().lock();
+ try {
+ for (Map.Entry extends K, ? extends V> 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