带过期时间的LRU

带过期时间的LRU实现
//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();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值