feat(core):实现Sieve缓存

This commit is contained in:
LettuceLeaves 2025-11-26 00:55:43 +08:00
parent 9e87ff60da
commit 2db66af020

View File

@ -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 缓存算法实现<br>
* <p>
* SIEVE 是一种比 LRU 更简单且通常更高效的缓存算法<br>
* 它不需要在缓存命中时移动节点仅需设置一个访问位<br>
* 驱逐时使用一个指针Hand扫描队列淘汰未被访问的节点<br>
* 驱逐时遇到已访问的节点会设置为未访问用于淘汰过期节点<br>
* </p>
*
* @param <K> 键类型
* @param <V> 值类型
* @author Lettuceleaves
*/
public class SieveCache<K, V> extends LockedCache<K, V> {
@Serial
private static final long serialVersionUID = 1L;
/**
* 双向链表头节点
*/
private SieveCacheObj<K, V> head;
/**
* 双向链表尾节点
*/
private SieveCacheObj<K, V> tail;
/**
* 下一次扫描的起始位置
*/
private SieveCacheObj<K, V> hand;
/**
* 构造<br>
* 默认无超时
*
* @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<K> keyObj = MutableObj.of(key);
SieveCacheObj<K, V> co = (SieveCacheObj<K, V>) cacheMap.get(keyObj);
if (co != null) {
final SieveCacheObj<K, V> 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<K, V> oldNode, SieveCacheObj<K, V> 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<K, V> getOrRemoveExpiredWithoutLock(K key) {
final Mutable<K> keyObj = MutableObj.of(key);
final SieveCacheObj<K, V> co = (SieveCacheObj<K, V>) 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<K, V> removeWithoutLock(K key) {
final Mutable<K> keyObj = MutableObj.of(key);
final SieveCacheObj<K, V> co = (SieveCacheObj<K, V>) 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<CacheObj<K, V>> values = cacheObjIter();
CacheObj<K, V> co;
while (values.hasNext()) {
co = values.next();
if (co.isExpired()) {
values.remove();
removeNode((SieveCacheObj<K, V>) co);
onRemove(co.key, co.obj);
count++;
}
}
}
while (isFull() && hand != null) {
if (!hand.visited) {
final SieveCacheObj<K, V> victim = hand;
hand = hand.prev;
final Mutable<K> 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<K, V> 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<K, V> 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<K, V> extends CacheObj<K, V> {
@Serial
private static final long serialVersionUID = 1L;
/**
* 是否被访问过
*/
boolean visited = false;
/**
* 前向节点
*/
SieveCacheObj<K, V> prev;
/**
* 后向节点
*/
SieveCacheObj<K, V> next;
protected SieveCacheObj(final K key, final V obj, final long ttl) {
super(key, obj, ttl);
}
}
}