diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/SieveCache.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/SieveCache.java new file mode 100644 index 000000000..9a73a9d55 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/SieveCache.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2013-2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.v7.core.cache.impl; + +import cn.hutool.v7.core.lang.mutable.Mutable; +import cn.hutool.v7.core.lang.mutable.MutableObj; + +import java.io.Serial; +import java.util.HashMap; +import java.util.Iterator; +import java.util.concurrent.locks.ReentrantLock; + +/** + * SIEVE 缓存算法实现
+ *

+ * SIEVE 是一种比 LRU 更简单且通常更高效的缓存算法
+ * 它不需要在缓存命中时移动节点,仅需设置一个访问位
+ * 驱逐时使用一个指针(Hand)扫描队列,淘汰未被访问的节点
+ * 驱逐时遇到已访问的节点会设置为未访问,用于淘汰过期节点
+ *

+ * + * @param 键类型 + * @param 值类型 + * @author Lettuceleaves + */ +public class SieveCache extends LockedCache { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 双向链表头节点 + */ + private SieveCacheObj head; + /** + * 双向链表尾节点 + */ + private SieveCacheObj tail; + /** + * 下一次扫描的起始位置 + */ + private SieveCacheObj hand; + + /** + * 构造
+ * 默认无超时 + * + * @param capacity 容量 + */ + public SieveCache(int capacity) { + this(capacity, 0); + } + + /** + * 构造 + * + * @param capacity 容量 + * @param timeout 默认超时时间,单位:毫秒 + */ + public SieveCache(int capacity, long timeout) { + if (Integer.MAX_VALUE == capacity) { + capacity -= 1; + } + + this.capacity = capacity; + this.timeout = timeout; + + // 这里的设置 capacity + 1, 1.0f 避免触发扩容 + this.cacheMap = new HashMap<>(capacity + 1, 1.0f); + this.lock = new ReentrantLock(); + } + + + @Override + protected void putWithoutLock(K key, V object, long timeout) { + final Mutable keyObj = MutableObj.of(key); + SieveCacheObj co = (SieveCacheObj) cacheMap.get(keyObj); + + if (co != null) { + final SieveCacheObj newCo = new SieveCacheObj<>(key, object, timeout); + + // 新加入的节点,默认刚访问过,防止立刻被淘汰 + newCo.visited = true; + + // 替换旧节点 + replaceNode(co, newCo); + cacheMap.put(keyObj, newCo); + + } else { + if (isFull()) { + pruneCache(); + } + co = new SieveCacheObj<>(key, object, timeout); + cacheMap.put(keyObj, co); + addToHead(co); + co.visited = false; + } + } + + /** + * 在双向链表中用 newNode 替换 oldNode,保持链表结构不变 + * + * @param oldNode 旧节点 + * @param newNode 新节点 + */ + private void replaceNode(SieveCacheObj oldNode, SieveCacheObj newNode) { + newNode.prev = oldNode.prev; + newNode.next = oldNode.next; + + // 更新前向指针 + if (oldNode.prev != null) { + oldNode.prev.next = newNode; + } else { + head = newNode; + } + + // 更新后向指针 + if (oldNode.next != null) { + oldNode.next.prev = newNode; + } else { + tail = newNode; + } + + oldNode.prev = null; + oldNode.next = null; + } + + @Override + protected CacheObj getOrRemoveExpiredWithoutLock(K key) { + final Mutable keyObj = MutableObj.of(key); + final SieveCacheObj co = (SieveCacheObj) cacheMap.get(keyObj); + + if (null != co) { + if (co.isExpired()) { + removeWithoutLock(key); + return null; + } + + co.visited = true; + co.lastAccess = System.currentTimeMillis(); + } + return co; + } + + @Override + protected CacheObj removeWithoutLock(K key) { + final Mutable keyObj = MutableObj.of(key); + final SieveCacheObj co = (SieveCacheObj) cacheMap.remove(keyObj); + if (co != null) { + removeNode(co); + } + return co; + } + + + /** + * 优先清理过期对象,如果容量仍溢出,反向扫描visited为false的节点,设置true节点为false + */ + @Override + protected int pruneCache() { + int count = 0; + + if (isPruneExpiredActive()) { + final Iterator> values = cacheObjIter(); + CacheObj co; + while (values.hasNext()) { + co = values.next(); + if (co.isExpired()) { + values.remove(); + removeNode((SieveCacheObj) co); + onRemove(co.key, co.obj); + count++; + } + } + } + + while (isFull() && hand != null) { + if (!hand.visited) { + final SieveCacheObj victim = hand; + hand = hand.prev; + final Mutable keyObj = MutableObj.of(victim.key); + cacheMap.remove(keyObj); + removeNode(victim); + onRemove(victim.key, victim.obj); + count++; + } else { + + // 近期被访问过,就先设置为false + hand.visited = false; + hand = hand.prev; + if (hand == null) { + + // 没找到能淘汰的节点,重新扫描一边 + hand = tail; + } + } + } + return count; + } + + + /** + * 将节点加入链表头部 + * + * @param node 节点 + */ + private void addToHead(SieveCacheObj node) { + node.next = head; + node.prev = null; + if (head != null) { + head.prev = node; + } + head = node; + if (tail == null) { + tail = node; + } + } + + /** + * 从链表中移除节点 + * + * @param node 节点 + */ + private void removeNode(SieveCacheObj node) { + if (node == hand) { + hand = node.prev; + } + + if (node.prev != null) { + node.prev.next = node.next; + } else { + head = node.next; + } + + if (node.next != null) { + node.next.prev = node.prev; + } else { + tail = node.prev; + } + + node.next = null; + node.prev = null; + } + + /** + * 给节点添加visited属性,用于Sieve缓存淘汰策略 + */ + private static class SieveCacheObj extends CacheObj { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 是否被访问过 + */ + boolean visited = false; + /** + * 前向节点 + */ + SieveCacheObj prev; + /** + * 后向节点 + */ + SieveCacheObj next; + + protected SieveCacheObj(final K key, final V obj, final long ttl) { + super(key, obj, ttl); + } + } +}