【数据结构与算法】通过双向链表和HashMap实现LRU缓存 详解

这个双向链表采用的是有伪头节点和伪尾节点的 与上一篇文章中单链表的实现不同,区别于在实例化这个链表时就初始化了的伪头节点和伪尾节点,并相互指向,在第一次添加节点时,不需要再考虑空指针指向问题了。

java 复制代码
/**
 * 通过链表与HashMap实现LRU缓存
 *
 * @author CC
 * @version 1.0
 * @since2023/9/27
 */
public class LRUCache {


    private Map<Integer, Node> cache = new HashMap<>();//哈希表

    private int size;//链表长度

    private int capacity;//缓存容量

    private Node first;//伪头节点
    private Node last;//伪尾节点

    /**
     * 将一个新节点添加到头部
     *
     * @param newNode 要添加的新节点
     */
    private void addFirst(Node newNode) {
        //注意: 顺序很重要
        //1、分配新节点的前驱和后继
        newNode.prev = first;
        newNode.next = first.next;

        //2、头节点原来的后继的前驱指向新节点
        first.next.prev = newNode;
        //3、头节点的后继执行新节点
        first.next = newNode;

    }

    /**
     * 删除一个节点
     *
     * @param node 要删除的节点
     */
    private void deleteNode(Node node) {
        //要删除节点的后继和前驱相互指引
        node.prev.next = node.next;
        node.next.prev = node.prev;

    }

    /**
     * 将一个节点放到伪头节点后
     *
     * @param node 移动的节点
     */
    private void moveToFirst(Node node) {
        //删除这个节点
        deleteNode(node);
        //添加一个头节点
        addFirst(node);
    }

    /**
     * 删除尾节点
     *
     * @return 返回删除的这个节点
     */
    private Node deleteToLast() {
        //获得伪尾节点的前驱 也就是尾节点
        Node ret = last.prev;
        //删除尾节点
        deleteNode(last.prev);
        return ret;
    }

    /**
     * 存入缓存
     *
     * @param key
     * @param value
     */
    public void put(int key, int value) {
        //从hash表中查询这个健
        Node node = cache.get(key);

        //如果hash表中不存在要添加的健
        if (node == null) {
            //创建一个新的节点
            Node newNode = new Node(key, value);
            //将这个健和节点添加到hash表中
            cache.put(key, newNode);
            //将这个节点存到头节点中
            addFirst(newNode);
            //如果这个缓存已满
            if (++size > capacity) {
                //删除尾节点
                Node last = deleteToLast();
                //从hash表中也删除这个健
                cache.remove(last.key);
                size--;
            }
            //如果hash表中存在要添加的健
        } else {
            //将新添加的值覆盖原来的值
            node.value = value;
            //并移到头节点
            moveToFirst(node);
        }
    }

    /**
     * 获取缓存
     *
     * @param key 该缓存的健
     * @return 返回 该节点的值
     */
    public int get(int key) {
        //通过健从hash表中获取这个节点
        Node node = cache.get(key);
        //如果为空 则返回-1
        if (node == null) {
            return -1;
        }
        //否则 将该节点 移到头节点处
        moveToFirst(node);
        return node.value;
    }

    /**
     * 双向链表的遍历 头->尾
     *
     * @return
     */
    @Override
    public String toString() {
        StringJoiner sj = new StringJoiner("->");
        for (Node n =first.next;n.next!=null;n=n.next){
            sj.add(String.valueOf(n.value));
        }
        return "头->尾:"+sj.toString();
    }


    /**
     * 构造方法
     *
     * @param capacity 设置缓存容量
     */
    public LRUCache(int capacity) {
        size = 0;//初始链表长度位0
        this.capacity = capacity;//设置缓存容量

        first = new Node();//实例化伪头节点
        last = new Node();//实例化伪尾节点

        //初始头尾节点相互指向
        first.next = last;
        last.prev = first;
    }


    /**
     * 节点类
     */
    class Node {
        int key; //键
        int value;//值
        Node prev;//前驱
        Node next;//后继

        /**
         * 无参构造
         */
        public Node() {
        }

        /**
         * 有参构造
         *
         * @param key   健
         * @param value 值
         */
        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }


}

测试

java 复制代码
        //实例一个缓存大小为7的LRU缓存
        LRUCache lruCache =new LRUCache(5);
        lruCache.put(1,1);
        lruCache.put(2,2);
        lruCache.put(3,3);
        lruCache.put(4,4);
        lruCache.put(5,5);
        lruCache.put(6,6);

        System.out.println("依次存入1、2、3、4、5、6后的缓存:"+lruCache);
        int l1 = lruCache.get(1);
        System.out.println("取出1后的缓存:"+lruCache+",取出的值:"+l1);

        int l2 = lruCache.get(2);
        System.out.println("取出2后的缓存:"+lruCache+",取出的值:"+l2);

        int l3 = lruCache.get(3);
        System.out.println("取出3后的缓存:"+lruCache+",取出的值:"+l3);

        lruCache.put(9,9);
        System.out.println("存入9后的缓存:"+lruCache);

测试结果

相关推荐
罗西的思考1 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX2 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面3 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号3 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽3 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI3 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者5 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者5 小时前
【征程6】校准量化中HistogramObserver解析
算法
OuO-25 小时前
笔试强训 Day 34:ISBN 号码、kotori 和迷宫、矩阵最长递增路径
java·算法·矩阵
程序喵大人6 小时前
【C++进阶】STL算法与函数对象 - 04 find、count和any_of把查询写成意图
开发语言·c++·算法