如何模拟实现一个“缓存”?

news2024/11/25 14:42:03

目录

前言

一、LRU Cache是什么

 

二、模拟实现

2.1、通过继承 LinkedHashMap 模拟实现

2.2、自主模拟实现LRU Cache

2.2.1、LRU Cache的定义

2.2.2、存放结点

2.2.3、访问结点

2.2.4、LRU Cache 完整模拟代码

小结


前言

        这次主要实现一个类似缓存的一种数据结构,缓存(Cache)容量有限,当容量用完后有新的数据添加进来,就需要将原来不常用的数据清除掉,再加入新的数据;


一、LRU Cache是什么

        LRU Cache的底层是一个双向链表 + 哈希表的结构,这样做是为了要达到一个时间复杂度为O(1)的存放元素以及获取元素(双向链表插入删除元素和哈希表查找元素可以到达O(1)),功能是将经常使用的元素放在链表的尾部,不常使用的放在链表的头部,当容量用完了后再添加元素时,就会把头部不经常用的元素删除掉;


二、模拟实现

2.1、通过继承 LinkedHashMap 模拟实现

        LinkedHashMap 底层也是双向链表 + 哈希表,其中有一个构造方法,若参数置为 false 时,会按照插入顺序进行排序,若参数置为 true 时,会按照访问顺序(也就是插入和访问都会将当前节点放置到尾部,尾部代表的是最近访问的数据),我们这里需要模拟实现 LRU Cache ,所以参数需要设置为 true ;

代码如下:

import java.util.LinkedHashMap;
import java.util.Map;

public class LRUCache extends LinkedHashMap<Integer, Integer> {

    public int capacity;//容量
    public LRUCache(int capacity) {
        //这里的true代表基于访问顺序
        super(capacity, 0.75F, true);
        this.capacity = capacity;//指定容量
    }

    @Override
    public Integer get(Object key) {
        return super.getOrDefault(key, -1);
    }

    public Integer put(Integer key, Integer value) {
        return super.put(key, value);
    }

    /**
     * 为什么要重写这个方法?
     * 因为达到某个条件为 true 就会删除头节点
     * @param eldest
     * @return
     */
    @Override
    protected  boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
        return size() > capacity;
    }

}

2.2、自主模拟实现LRU Cache

2.2.1、LRU Cache的定义

这里只需要顶一个双向链表和一个HashMap即可;

import java.util.HashMap;
import java.util.Map;

public class MyLRUCache {
    //双向链表
    static class DLinkNode {
        public int key;
        public int value;
        public DLinkNode prev;
        public DLinkNode next;

        public DLinkNode() {

        }

        public DLinkNode(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    public DLinkNode head;//双向链表的头节点
    public DLinkNode tail;//双向链表的尾结点
    public int usedSize;//当前双向链表中有效数据的个数
    public Map<Integer, DLinkNode> map;
    public int capacity;//容量

    public MyLRUCache(int capacity) {
        this.head = new DLinkNode();
        this.tail = new DLinkNode();
        head.next = tail;
        tail.prev = head;
        this.cache = new HashMap<>();
        this.capacity = capacity;
    }


}

2.2.2、存放结点

        首先检查当前结点是否之前存放过,若没有存放过,就将这个结点存储到尾巴的位置,若之前有存储过该节点,就把之前的结点移动到尾巴的位置即可;


    /**
     * 存储元素
     * @param key
     * @param val
     */
    public void put(int key, int val) {
        //1.查找当前这个keu是不是存储过;
        DLinkNode node = cache.get(key);
        //2.如果没有存储过
        if(node == null) {
            //2.1需要实例化一个结点
            DLinkNode dLinkNode = new DLinkNode(key, val);
            //2.2存储到map中
            cache.put(key, dLinkNode);
            //2.3把该结点存储到链表尾巴
            addToTail(dLinkNode);
            usedSize++;
            //2.4检查当前双向链表的有效数据个数 是不是超过了capacity
            if(usedSize > capacity) {
                //2.5超过了就要移除头部结点
                DLinkNode remNode = removeHead();
                //2.6清除cache中的元素
                cache.remove(remNode.key);
                //2.6usedSize--;
                usedSize--;
            }
        } else {
            //3.如果存储过
            //3.1更新这个key对应的val
            node.val = val;
            //3.2然后将该结点移到尾部(因为这个是新插入的数据)
            moveToTail(node);
        }
    }

    /**
     * 移动当前结点到尾巴结点
     * @param node
     */
    private void moveToTail(DLinkNode node) {
        //1.先删除这个结点
        removeNode(node);
        //2.添加到尾部
        addToTail(node);
    }

    /**
     * 删除指定结点
     * @param node
     */
    private void removeNode(DLinkNode node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }


    /**
     * 添加结点到链表的尾部
     * @param node
     */
    private void addToTail(DLinkNode node) {
        tail.prev.next = node;
        node.prev = tail.prev;
        node.next = tail;
        tail.prev = node;
    }

    /**
     * 删除头部结点
     * @return
     */
    private DLinkNode removeHead() {
        DLinkNode del = head.next;
        head.next = del.next;
        del.next.prev = head;
        return del;
    }

2.2.3、访问结点

实际上就是将访问到的结点放到尾巴结点处即可(经常使用的)

    /**
     * 访问当前key
     * 逻辑:将访问的结点放到尾巴
     * @param key
     * @return
     */
    public int get(int key) {
        DLinkNode node = cache.get(key);
        if(node == null) {
            return -1;
        }
        //把最近经常使用的放到尾巴
        moveToTail(node);
        return node.val;
    }

2.2.4、LRU Cache 完整模拟代码

import java.util.HashMap;
import java.util.Map;

public class MyLRUCache {
    //双向链表
    static class DLinkNode {
        public int key;
        public int val;
        public DLinkNode prev;
        public DLinkNode next;

        public DLinkNode() {

        }

        public DLinkNode(int key, int value) {
            this.key = key;
            this.val = value;
        }

        @Override
        public String toString() {
            return "key=" + key +
                    ", val=" + val;
        }
    }

    public DLinkNode head;//双向链表的头节点
    public DLinkNode tail;//双向链表的尾结点
    public int usedSize;//当前双向链表中有效数据的个数
    public Map<Integer, DLinkNode> cache;
    public int capacity;//容量

    public MyLRUCache(int capacity) {
        this.head = new DLinkNode();
        this.tail = new DLinkNode();
        head.next = tail;
        tail.prev = head;
        this.cache = new HashMap<>();
        this.capacity = capacity;
    }

    /**
     * 存储元素
     * @param key
     * @param val
     */
    public void put(int key, int val) {
        //1.查找当前这个keu是不是存储过;
        DLinkNode node = cache.get(key);
        //2.如果没有存储过
        if(node == null) {
            //2.1需要实例化一个结点
            DLinkNode dLinkNode = new DLinkNode(key, val);
            //2.2存储到map中
            cache.put(key, dLinkNode);
            //2.3把该结点存储到链表尾巴
            addToTail(dLinkNode);
            usedSize++;
            //2.4检查当前双向链表的有效数据个数 是不是超过了capacity
            if(usedSize > capacity) {
                //2.5超过了就要移除头部结点
                DLinkNode remNode = removeHead();
                //2.6清除cache中的元素
                cache.remove(remNode.key);
                //2.6usedSize--;
                usedSize--;
            }
        } else {
            //3.如果存储过
            //3.1更新这个key对应的val
            node.val = val;
            //3.2然后将该结点移到尾部(因为这个是新插入的数据)
            moveToTail(node);
        }
    }

    /**
     * 移动当前结点到尾巴结点
     * @param node
     */
    private void moveToTail(DLinkNode node) {
        //1.先删除这个结点
        removeNode(node);
        //2.添加到尾部
        addToTail(node);
    }

    /**
     * 删除指定结点
     * @param node
     */
    private void removeNode(DLinkNode node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }


    /**
     * 添加结点到链表的尾部
     * @param node
     */
    private void addToTail(DLinkNode node) {
        tail.prev.next = node;
        node.prev = tail.prev;
        node.next = tail;
        tail.prev = node;
    }

    /**
     * 删除头部结点
     * @return
     */
    private DLinkNode removeHead() {
        DLinkNode del = head.next;
        head.next = del.next;
        del.next.prev = head;
        return del;
    }

    /**
     * 访问当前key
     * 逻辑:将访问的结点放到尾巴
     * @param key
     * @return
     */
    public int get(int key) {
        DLinkNode node = cache.get(key);
        if(node == null) {
            return -1;
        }
        //把最近经常使用的放到尾巴
        moveToTail(node);
        return node.val;
    }

    public void printNode(String str) {
        DLinkNode cur = head.next;
        while( cur != tail) {
            System.out.println(cur);
            cur = cur.next;
        }
    }
}

小结

建议多写多练~


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/167628.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

day49 业务逻辑水平垂直越权访问控制脆弱验证

前言&#xff1a; #知识点&#xff1a; 1、水平越权-同级用户权限共享 2、垂直越权-低高用户权限共享 3、访问控制-验证丢失&取消验证 4、脆弱验证-Cookie&Token&Jwt等 #前置知识&#xff1a; 1、逻辑越权原理- -水平越权&#xff1a;用户信息获取时未对用户…

NTP(Network Time Protocol)协议详解

一、NTP的基本概念&#xff1a; NTP(Network Time Protocol)------网络时间协议-----应用层协议&#xff0c;用来在分布式时间服务器和客户端之间进行时间同步。 二、采用NTP的目的&#xff1a; 是对网络内所有具有时钟的设备进行时钟同步&#xff0c;使网络内所有设备的时钟…

JDY-06蓝牙透传无天线模块介绍

JDY-06蓝牙透传无天线模块简介JDY-06透传模块是基于蓝牙4.0协议标准&#xff0c;工作频段为2.4GHZ范围&#xff0c;调制方式为GFSK&#xff0c;最大发射功率为0db&#xff0c;最大发射距离60米&#xff0c;采用TICC2541芯片设计&#xff0c;支持用户通过AT命令修改设备名、服务…

Alkyne maleimide,Mal-Alkyne,炔烃-马来酰亚胺

中英文别名&#xff1a;CAS号&#xff1a;N/A | 英文名&#xff1a;Alkyne maleimide&#xff0c;Mal-Alkyne |中文名&#xff1a;炔烃-马来酰亚胺物理参数&#xff1a;CASNumber&#xff1a;N/AMolecular formula&#xff1a;C12H14N2O3Molecular weight&#xff1a;234.26Pur…

什么是前后端分离?

当前&#xff0c;数字化发展速度越来越快&#xff0c;前后端分离已经成为今后的发展趋势。在现代化办公管理中&#xff0c;不少企业倾向于使用低代码开发平台实现高效协作办公&#xff0c;那么&#xff0c;什么是前后端分离&#xff1f;都有哪些特点&#xff1f;本文就围绕这个…

【网络安全】渗透测试之linux信息收集

前言 在内网中linux的服务器是占大多数的&#xff0c;主要原因分为以下几点 1.便宜&#xff0c;linux大多为免费的&#xff0c;Windows Server是收费的&#xff0c;对于企业来说为了节约成本&#xff0c;大量采用linux服务器。 2.轻便&#xff0c;linux主要是对服务器进行服务的…

递归算法实例应用(三)

递归算法实例应用&#xff08;三&#xff09; 四则运算表达式求值 Description 给你一个字符串表达式 str &#xff0c;请你实现一个基本计算器来计算并返回它的值。 注意:不允许使用任何将字符串作为数学表达式计算的内置函数 。 Input 一行&#xff0c;一个四则运算表达…

编写设备驱动之i2c_client

编写设备驱动之i2c_client 文章目录编写设备驱动之i2c_client参考资料&#xff1a;一、I2C总线-设备-驱动模型二、 编译i2c_driver三、编写测试程序四、多种方法生成i2c_client并测试4.1 在用户态生成4.2 编写代码4.3 使用设备树生成2. IMX6ULL4.4 上机测试致谢参考资料&#x…

prometheus的remotewrite解析

特性 目的是为了向远端的存储写入数据。 为了提高写入效率&#xff0c;Prometheus在将采集到的samples写入远程存储之前&#xff0c;会先缓存在内存队列中&#xff0c;然后打包发送给远端存储。而这个内存队列的配置参数&#xff0c;对于Prometheus写入远程存储的效率影响较大…

《MySQL高级篇》十一、事务基础知识

文章目录1. 数据库事务概述1.1 存储引擎支持情况1.2 基本概念1.3 事务的ACID特性1.4 事务的状态2. 如何使用事务2.1 显式事务2.2 隐式事务2.3 隐式提交数据的情况2.4 使用举例1&#xff1a;提交与回滚2.5 使用举例2&#xff1a;测试不支持事务的Engine2.6 使用举例3&#xff1a…

STL中的队列用法整理

STL中的队列先进先出队列&#xff08;FIFO&#xff09;主要方法代码示例输出优先级队列模版原型主要对方法有代码示例int的大顶堆运行结果int的小顶堆运行结果使用自定义的比较函数的优先队列代码示例运行结果双端队列主要方法示例运行结果有关双端队列的题目先进先出队列&…

Vue生命周期,总也学不会,所以我详细整理了一下

今天&#xff0c;我和大家一起来对vue生命周期做一个整理和思考&#xff0c;希望有缘人看到我的年度整理和思考&#xff0c;如果觉得靠谱呢&#xff0c;就交个朋友&#xff0c;如果觉得我整理的不足&#xff0c;就请指出&#xff0c;让我们一起进步&#xff0c;让我们2023年能共…

单链表

插入&#xff1a; head 表示头结点的下标 e[i] 表示节点i的值 ne[i] 表示节点i的next指针是多少 idx 存储当前已经用到了哪个点 步骤&#xff1a;1.初始化head 2.将x插到头结点 3.插入&#xff1a;将x插到下标是k的点后面 4.将x插到下标是k的点后面 5.将下标是k的点后面…

springboot,vue影院订票系统

开发工具&#xff1a;IDEA服务器&#xff1a;Tomcat9.0&#xff0c; jdk1.8项目构建&#xff1a;maven数据库&#xff1a;mysql5.7系统用户前台和管理后台两部分&#xff0c;项目采用前后端分离前端技术&#xff1a;vue elementUI服务端技术&#xff1a;springbootmybatis项目功…

集合框架及背后的数据结构

集合框架及背后的数据结构1. 介绍2. 学习的意义2.1 Java 集合框架的优点及作用2.2 笔试及面试题3. 接口 interfaces3.1 基本关系说明3.2 Collection 接口说明3.3 Collection 常用方法说明3.4 Collection 示例3.5 Map 接口说明Map3.6 Map 常用方法说明3.7 Map 示例4. 实现 class…

免费AI改图神器,一个万能宝藏在线工具箱

说到工具箱&#xff0c;无论是在线工具&#xff0c;还是软件应用都非常多。 比如想要抠一张图片&#xff0c;如果专业处理&#xff0c;那么会使用到 Photoshop&#xff0c;需要一定的学习成本&#xff0c;想要更快捷处理&#xff0c;那么会直接使用在线工具&#xff0c;网络上…

Secret

目录 文章目录目录本节实战前言1、Opaque Secret1.创建Secret&#xff08;1&#xff09;通过data字段来创建secret资源对象&#xff08;2&#xff09;通过stringData字段来创建secret资源对象&#xff08;3&#xff09;通过kubectl create命令来创建Opaque类型的Secret资源2.使…

ROS2 基础概念 参数

ROS2 基础概念 参数1. Parameters2. 参数3. 参数查看4. 参数设置5. 参数保存6. 参数加载1. Parameters 指令功能ros2 param delete /node parameter删除参数值ros2 param describe /node parameter显示参数的相关描述ros2 param dump /file将参数保存到一个文件中ros2 param g…

LinuxDeployQT打包QT程序

系统&#xff1a;ubuntu20.04官网可直接下载使用https://github.com/probonopd/linuxdeployqt/releases&#xff0c;但是因为不支持ubuntu20所以本文通过下载源码编译的方式编译linuxdeployqt安装编译相关依赖sudo apt-get install git g libgl1-mesa-dev wget安装QTsudo apt-g…

大数据之Kafka Shell命令和Java API

文章目录前言一、Kafka相关Shell命令&#xff08;一&#xff09;创建并查询Topic&#xff08;二&#xff09;删除Topic&#xff08;三&#xff09;增加Topic的分区&#xff08;四&#xff09;生产数据到Topic&#xff08;五&#xff09;从Topic消费数据总结前言 #博学谷IT学习…