//import lombok.var;
import org.testng.annotations.Test;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 带过期时间的 LRU 缓存(非线程安全)
*
* @param <K> key 类型
* @param <V> value 类型
*/
public class ExpiringLRUCache<K, V> {
private final int maxSize;
private final long defaultExpireMillis;
/**
* 实际存储结构
*/
private final LinkedHashMap<K, CacheNode<V>> map;
public ExpiringLRUCache(int maxSize, long defaultExpireMillis) {
if (maxSize <= 0) throw new IllegalArgumentException("maxSize must be positive");
this.maxSize = maxSize;
this.defaultExpireMillis = defaultExpireMillis;
// accessOrder = true 表示按访问顺序排序,最近访问的在尾部
this.map = new LinkedHashMap<K, CacheNode<V>>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<K, CacheNode<V>> eldest) {
// 1. 先删过期
evictExpired();
// 2. 再判断是否超过容量
return size() > maxSize;
}
};
}
/**
* 放入缓存
*
* @param key key
* @param value value
* @param ttlMillis 过期时间(毫秒),<=0 则用默认
*/
public void put(K key, V value, long ttlMillis) {
long expireAt = System.currentTimeMillis() + (ttlMillis <= 0 ? defaultExpireMillis : ttlMillis);
map.put(key, new CacheNode<>(value, expireAt));
}
public void put(K key, V value) {
put(key, value, defaultExpireMillis);
}
/**
* 读取缓存
*/
public V get(K key) {
CacheNode<V> node = map.get(key);
if (node == null) return null;
// 惰性删除:如果过期了,直接删掉
if (node.isExpired()) {
map.remove(key);
return null;
}
return node.value;
}
/**
* 删除
*/
public V remove(K key) {
CacheNode<V> node = map.remove(key);
return node == null ? null : node.value;
}
/**
* 当前缓存大小
*/
public int size() {
evictExpired(); // 惰性回收
return map.size();
}
/**
* 清空
*/
public void clear() {
map.clear();
}
/**
* 遍历链表头,删除所有已过期节点
*/
private void evictExpired() {
Iterator<Map.Entry<K, CacheNode<V>>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<K, CacheNode<V>> entry = it.next(); // ← 这里改对
if (entry.getValue().isExpired()) {
it.remove();
} else {
// 链表越往后越新,遇到第一个未过期的就可以停了
break;
}
}
}
/**
* 内部包装:值 + 过期时间
*/
private static class CacheNode<V> {
final V value;
final long expireAt;
CacheNode(V value, long expireAt) {
this.value = value;
this.expireAt = expireAt;
}
boolean isExpired() {
return System.currentTimeMillis() > expireAt;
}
}
/* ------------------ 测试 ------------------ */
public static void main(String[] args) throws InterruptedException {
ExpiringLRUCache<String, String> cache = new ExpiringLRUCache<>(3, 1000);
cache.put("a", "A");
cache.put("b", "B");
cache.put("c", "C");
System.out.println(cache.get("a")); // A
Thread.sleep(1100); // 全部过期
System.out.println(cache.get("a")); // null
System.out.println(cache.size()); // 0
cache.put("d", "D", 500); // 500ms 过期
cache.put("e", "E", 2000); // 2s 过期
Thread.sleep(600);
System.out.println(cache.get("d")); // null
System.out.println(cache.get("e")); // E
System.out.println(cache.size()); // 1
}
}
下面这种get会更新时间,并将节点移动到链表尾部。
import java.util.*;
class LRUservice<K,V>{
private final int capacity;
private int size;
private final long defaultExpireTime ;
private HashMap<K,CacheNode<K,V>> map;
private CacheNode<K,V> head=null;
private CacheNode<K,V> tail=null;
//初始化lru
LRUservice(int capacity,long expireTime){
this.capacity = capacity;
this.defaultExpireTime = expireTime;
map = new HashMap<>();
head = new CacheNode<>(null,null,-1L);
tail = new CacheNode<>(null,null,-1L);
head.next = tail;
tail.pre = head;
}
//get时注意检查过期时间,如果过期了要删除
public V get(K key){
CacheNode<K,V> node = map.get(key);
if(node == null){return null;}
if(node.isExpired()){
deleteNode(node);
return null;
}
deleteAndAdd(node);
return node.value;
}
public void put(K key, V value){
put(key,value,defaultExpireTime);
}
public void put(K key, V value, long expireTime){
CacheNode<K,V> node = map.get(key);
if(node == null){
//当准备插入新节点时,先对可能过期的节点进行整改,这里直接从队头开始排查已经过期的节点,如果第一个节点设置的过期时间比较长,可能后面很多节点过期,但是头节点没过期,这就导致删除掉了没有过期的节点,这样主要是为了整理节点的时间复杂度接近O1,当然也可以继续整理,例如全表扫描并删除过期的,这里为了时间复杂度考虑就直接删除第一个节点了。
clearUp();
if(size == capacity){
deleteHead();
}
//每个小函数除了链表的变化,更新时间的变化也要注意map和size的变化,经常容易忘记
CacheNode<K,V> newNode = new CacheNode<>(key,value,expireTime);
size++;
map.put(key,newNode);
add(newNode);
}
else{
node.value = value;
node.expireTime = expireTime;
node.expireAt = expireTime+System.currentTimeMillis();
deleteAndAdd(node);
}
}
//从头删除一个节点
private void deleteHead(){
CacheNode<K,V> node = head.next;
//判断是否还有节点
if(node == tail){return;}
node.next.pre = head;
head.next = node.next;
size--;
map.remove(node.key);
}
//将节点移动到链表尾部
private void deleteAndAdd(CacheNode<K,V> node){
node.pre.next=node.next;
node.next.pre=node.pre;
node.expireAt = node.expireTime+System.currentTimeMillis();
add(node);
}
private void add(CacheNode<K,V> node){
node.pre = tail.pre;
node.next = tail;
node.pre.next = node;
tail.pre = node;
}
private void clearUp(){
CacheNode<K,V> node = head.next;
while(node != tail&& node.isExpired()){
CacheNode<K,V> next = node.next;
deleteHead();
node = next;
}
}
private void deleteNode(CacheNode<K,V> node){
node.pre.next = node.next;
node.next.pre = node.pre;
map.remove(node.key);
size--;
}
static class CacheNode<K,V>{
final K key;
V value;
CacheNode<K,V> pre;
CacheNode<K,V> next;
long expireAt;
long expireTime;
CacheNode(K key,V value, long expireTime){
this.key = key;
this.value = value;
this.expireTime = expireTime;
this.expireAt = System.currentTimeMillis()+expireTime;
}
boolean isExpired(){
return expireAt>0 && expireAt < System.currentTimeMillis();
}
}
}
带过期时间的LRU实现
154

被折叠的 条评论
为什么被折叠?



