设置一个带超时时间的LRU缓存

1.思路:需要在LRU(最近最少使用)的基础上继续实现。

(1)在定义双向链表节点Node的时候,给Node增加过期时间戳字段expireTime(表示该节点的过期时间是多少)和检查节点是否过期的成员方法(实例方法)boolean isExpired()(如果当前时间的毫秒数System.currentTimeMillis() > expireTime,那么就return true,表示该节点已经过期)。

后续更新


附代码:

java 复制代码
class TimeoutLRUCache {

    // 双向链表节点,增加过期时间戳字段
    private static class Node {
        int key, value;
        long expireTime; // 过期时间戳(毫秒)
        Node prev, next;

        Node(int key, int value, long expireTime) {
            this.key = key;
            this.value = value;
            this.expireTime = expireTime;
            this.prev = null;
            this.next = null;
        }

        // 检查节点是否已过期
        boolean isExpired() {
            return System.currentTimeMillis() > expireTime;
        }
    }

    private final int capacity;
    private final long defaultTtl; // 默认过期时间(毫秒)
    private final Node sentinel = new Node(0, 0, Long.MAX_VALUE);
    private final Map<Integer, Node> keyToNode = new HashMap<>();

    // 构造函数:只指定容量,使用默认过期时间(5分钟)
    public TimeoutLRUCache(int capacity) {
        this(capacity, 300000); // 默认5分钟
    }

    // 构造函数:指定容量和默认过期时间(毫秒)
    public TimeoutLRUCache(int capacity, long defaultTtl) {
        this.capacity = capacity;
        this.defaultTtl = defaultTtl;
        sentinel.prev = sentinel;
        sentinel.next = sentinel;
    }

    // 获取值,如果已过期则返回 -1
    public int get(int key) {
        Node node = getNode(key);
        if (node == null || node.isExpired()) {
            if (node != null) {
                // 过期节点从缓存中移除
                remove(node);
                keyToNode.remove(key);
            }
            return -1;
        }
        return node.value;
    }

    // 放入缓存,使用默认过期时间
    public void put(int key, int value) {
        put(key, value, defaultTtl);
    }

    // 放入缓存,指定过期时间(毫秒)
    public void put(int key, int value, long ttlMillis) {
        Node node = getNode(key);
        if (node != null) {
            // 更新存在的节点
            node.value = value;
            node.expireTime = System.currentTimeMillis() + ttlMillis;
            return;
        }

        // 创建新节点
        long expireTime = System.currentTimeMillis() + ttlMillis;
        node = new Node(key, value, expireTime);
        keyToNode.put(key, node);
        pushFront(node);

        // 清理过期节点 + 容量控制
        cleanExpiredNodes();
        if (keyToNode.size() > capacity) {
            Node backNode = sentinel.prev;
            // 从后往前清理,优先清理过期节点
            while (backNode != sentinel && keyToNode.size() > capacity) {
                if (backNode.isExpired()) {
                    Node toRemove = backNode;
                    backNode = backNode.prev;
                    keyToNode.remove(toRemove.key);
                    remove(toRemove);
                } else if (keyToNode.size() > capacity) {
                    // 容量超限,移除最久未使用的节点
                    keyToNode.remove(backNode.key);
                    remove(backNode);
                    backNode = sentinel.prev;
                }
            }
        }
    }

    // 获取节点并移到头部
    private Node getNode(int key) {
        if (!keyToNode.containsKey(key)) {
            return null;
        }
        Node node = keyToNode.get(key);
        // 如果节点已过期,返回null(让调用方处理)
        if (node.isExpired()) {
            return null;
        }
        remove(node);
        pushFront(node);
        return node;
    }

    // 清理所有过期节点
    private void cleanExpiredNodes() {
        Node current = sentinel.prev;
        while (current != sentinel) {
            Node next = current.prev;
            if (current.isExpired()) {
                keyToNode.remove(current.key);
                remove(current);
            }
            current = next;
        }
    }

    // 从链表中移除节点
    private void remove(Node x) {
        x.prev.next = x.next;
        x.next.prev = x.prev;
    }

    // 将节点插入链表头部
    private void pushFront(Node x) {
        x.prev = sentinel;
        x.next = sentinel.next;
        x.prev.next = x;
        x.next.prev = x;
    }

    // 获取当前缓存大小(包含未过期节点)
    public int size() {
        cleanExpiredNodes();
        return keyToNode.size();
    }

    // 检查缓存是否包含某个key(未过期)
    public boolean containsKey(int key) {
        Node node = keyToNode.get(key);
        return node != null && !node.isExpired();
    }
}

ACM模式:

java 复制代码
import java.util.*;

class TimeoutLRUCache {

    // 双向链表节点,增加过期时间戳字段
    private static class Node {
        int key, value;
        long expireTime; // 过期时间戳(毫秒)
        Node prev, next;

        Node(int key, int value, long expireTime) {
            this.key = key;
            this.value = value;
            this.expireTime = expireTime;
            this.prev = null;
            this.next = null;
        }

        // 检查节点是否已过期
        boolean isExpired() {
            return System.currentTimeMillis() > expireTime;
        }
    }

    private final int capacity;
    private final long defaultTtl; // 默认过期时间(毫秒)
    private final Node sentinel = new Node(0, 0, Long.MAX_VALUE);
    private final Map<Integer, Node> keyToNode = new HashMap<>();

    // 构造函数:只指定容量,使用默认过期时间(5分钟)
    public TimeoutLRUCache(int capacity) {
        this(capacity, 300000); // 默认5分钟
    }

    // 构造函数:指定容量和默认过期时间(毫秒)
    public TimeoutLRUCache(int capacity, long defaultTtl) {
        this.capacity = capacity;
        this.defaultTtl = defaultTtl;
        sentinel.prev = sentinel;
        sentinel.next = sentinel;
    }

    // 获取值,如果已过期则返回 -1
    public int get(int key) {
        Node node = getNode(key);
        if (node == null || node.isExpired()) {
            if (node != null) {
                // 过期节点从缓存中移除
                remove(node);
                keyToNode.remove(key);
            }
            return -1;
        }
        return node.value;
    }

    // 放入缓存,使用默认过期时间
    public void put(int key, int value) {
        put(key, value, defaultTtl);
    }

    // 放入缓存,指定过期时间(毫秒)
    public void put(int key, int value, long ttlMillis) {
        Node node = getNode(key);
        if (node != null) {
            // 更新存在的节点
            node.value = value;
            node.expireTime = System.currentTimeMillis() + ttlMillis;
            return;
        }

        // 创建新节点
        long expireTime = System.currentTimeMillis() + ttlMillis;
        node = new Node(key, value, expireTime);
        keyToNode.put(key, node);
        pushFront(node);

        // 清理过期节点 + 容量控制
        cleanExpiredNodes();
        if (keyToNode.size() > capacity) {
            Node backNode = sentinel.prev;
            // 从后往前清理,优先清理过期节点
            while (backNode != sentinel && keyToNode.size() > capacity) {
                if (backNode.isExpired()) {
                    Node toRemove = backNode;
                    backNode = backNode.prev;
                    keyToNode.remove(toRemove.key);
                    remove(toRemove);
                } else if (keyToNode.size() > capacity) {
                    // 容量超限,移除最久未使用的节点
                    keyToNode.remove(backNode.key);
                    remove(backNode);
                    backNode = sentinel.prev;
                }
            }
        }
    }

    // 获取节点并移到头部
    private Node getNode(int key) {
        if (!keyToNode.containsKey(key)) {
            return null;
        }
        Node node = keyToNode.get(key);
        // 如果节点已过期,返回null(让调用方处理)
        if (node.isExpired()) {
            return null;
        }
        remove(node);
        pushFront(node);
        return node;
    }

    // 清理所有过期节点
    private void cleanExpiredNodes() {
        Node current = sentinel.prev;
        while (current != sentinel) {
            Node next = current.prev;
            if (current.isExpired()) {
                keyToNode.remove(current.key);
                remove(current);
            }
            current = next;
        }
    }

    // 从链表中移除节点
    private void remove(Node x) {
        x.prev.next = x.next;
        x.next.prev = x.prev;
    }

    // 将节点插入链表头部
    private void pushFront(Node x) {
        x.prev = sentinel;
        x.next = sentinel.next;
        x.prev.next = x;
        x.next.prev = x;
    }

    // 获取当前缓存大小(包含未过期节点)
    public int size() {
        cleanExpiredNodes();
        return keyToNode.size();
    }

    // 检查缓存是否包含某个key(未过期)
    public boolean containsKey(int key) {
        Node node = keyToNode.get(key);
        return node != null && !node.isExpired();
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        int n = scanner.nextInt();
        TimeoutLRUCache cache = null;
        List<String> output = new ArrayList<>();

        for (int i = 0; i < n; i++) {
            String op = scanner.next();

            switch (op) {
                case "TimeoutLRUCache":
                    int capacity = scanner.nextInt();
                    cache = new TimeoutLRUCache(capacity);
                    output.add("null");
                    break;

                case "put":
                    int key = scanner.nextInt();
                    int value = scanner.nextInt();
                    // 可选:读取过期时间,如果没有则使用默认值
                    long ttl = -1;
                    if (scanner.hasNextInt()) {
                        ttl = scanner.nextInt();
                    }
                    if (ttl > 0) {
                        cache.put(key, value, ttl);
                    } else {
                        cache.put(key, value);
                    }
                    output.add("null");
                    break;

                case "putWithTtl":
                    int k = scanner.nextInt();
                    int v = scanner.nextInt();
                    long ttlMillis = scanner.nextLong();
                    cache.put(k, v, ttlMillis);
                    output.add("null");
                    break;

                case "get":
                    int getKey = scanner.nextInt();
                    int result = cache.get(getKey);
                    output.add(String.valueOf(result));
                    break;

                case "size":
                    output.add(String.valueOf(cache.size()));
                    break;

                case "contains":
                    int checkKey = scanner.nextInt();
                    output.add(String.valueOf(cache.containsKey(checkKey)));
                    break;

                default:
                    output.add("unknown");
            }
        }

        // 输出结果
        for (int i = 0; i < output.size(); i++) {
            System.out.print(output.get(i));
            if (i < output.size() - 1) {
                System.out.print(" ");
            }
        }

        scanner.close();
    }
}
相关推荐
ofoxcoding2 天前
在AI API聚合平台配置DeepSeek V3.2提示词缓存实战:快速接入与成本优化指南
人工智能·spring·缓存·ai
NeilYuen2 天前
gRPC结合FAISS构建AI助手语义缓存模块(一):设计
人工智能·缓存·faiss
taocarts_bidfans2 天前
反向海淘跨境缓存架构优化:taocarts Redis分层缓存实战技术
redis·缓存·架构·反向海淘·taocarts
退休倒计时2 天前
【每日一题】LeetCode 146. LRU 缓存 TypeScript
算法·leetcode·缓存·typescript
炘爚2 天前
Linux——Redis
数据库·redis·缓存
小挪号底迪滴2 天前
Redis 和 MySQL 数据不一致怎么办?缓存更新策略实战
redis·mysql·缓存
闪电悠米2 天前
黑马点评-Redis ZSet-实现关注 Feed 流
服务器·网络·数据库·redis·缓存·junit·lua
Saniffer_SH3 天前
【高清视频】Gen6 服务器还没到,Gen6 SSD 怎么测?Emily 现场演示三种测试环境
人工智能·驱动开发·测试工具·缓存·fpga开发·计算机外设·压力测试
AC赳赳老秦3 天前
OpenClaw + 飞书多维表格:自动同步数据、生成统计图表、触发自动化任务
java·大数据·python·缓存·自动化·deepseek·openclaw