【数据结构与算法】通过双向链表和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);

测试结果

相关推荐
CV工程师丁Sir5 小时前
ArkWeb 手记 04|DevTools 调试与缓存清理
java·spring·缓存·harmonyos
yi01112 小时前
LeetCode 219:存在重复元素 II——哈希表记录“最近一次出现的位置”
数据结构·人工智能·笔记·python·算法·leetcode·哈希表
志尊宝13 小时前
Vue3 零基础每日笔记(073):keep-alive 页面缓存实战——列表页返回不丢状态
vue.js·笔记·缓存·#vue #前端 #前端开发·#javascript·#vue.js
禾小西14 小时前
Redis:从两大维度和三大主线建立知识体系
数据库·redis·缓存
灯澜忆梦15 小时前
【面向对象编程C++】| 基础语法
java·c++·算法
小蒋观天下15 小时前
专项方案:大场景港口AI安防、多干扰环境下的算法调优与落地实操
人工智能·深度学习·算法·安全·机器学习·计算机视觉·ai大模型
Evand J15 小时前
【电机滤波例程5】渐消因子自适应扩展卡尔曼滤波(EKF),MATLAB。PMSM负载突变下的状态估计。附下载链接
算法·matlab
H.莓飛15 小时前
【数据结构】二叉树_OJ题
linux·开发语言·数据结构·算法
好想像大佬一样能够ak所有16 小时前
Leetcode35.搜索插入位置
数据结构·算法·leetcode
aqiu11111116 小时前
【C++算法打怪专栏】LeetCode 165. 比较版本号(双指针与字符串流法)
c++·算法·leetcode