【算法】不使用LinkedHashMap实现一个LRU缓存

文章目录

  • 什么是LRU?
  • 设计思路
  • 代码实现

LRU是我在面试过程中遇到的比较多的算法题了,并且我自己的项目中也手写了LRU算法,所以觉得还是有必要掌握一下这个重要的算法的。

什么是LRU?

LRU是一种缓存淘汰策略。

我们知道,计算机的缓存容量有限,如果缓存占用满了,那么我们就需要删除一些旧数据,并且把新数据放进来,那么问题就是,我们应该选择删除什么数据呢?或者说,我们应该使用一种什么样子的策略来删除数据呢?

LRU 缓存淘汰算法就是一种常用策略。LRU 的全称是 Least Recently Used,也叫“最近最少使用”算法,它意味着我们需要删除的数据是那些在最近一段时间内,最少使用的哪些数据,因为它认为在这段时间内使用最频繁的数据还有可能继续被使用,而那些很久没有被使用的数据很可能不会再次被使用。

这就是LRU(Least Recently Used)策略。与此同时还有其他缓存淘汰策略,比如按访问频率(LFU 策略)来淘汰等等,各有应用场景。本文讲解 LRU 算法策略。

并且,我们使用的这种算法,不能因为使用了这种算法,导致降低对缓存这种高速缓冲区的访问速度,因此,我们要求,我们的算法的时间复杂度是O(1)。
也就是我们放入以及查询元素的时间复杂度都必须是O(1)。

设计思路

从上面的对LRU的了解我们可以知道,LRU算法需要满足如下几个要求:
1:首先这个数据结构必须是有时序的,以区分最近使用的和很久没有使用的数据,当容量满了之后,要删除最久未使用的那个元素。
2:要在这个数据结构中快速找到某个 key 是否存在,并返回其对应的 value。
3:每次访问这个数据结构中的某个 key,需要将这个元素变为最近使用的。也就是说,这个数据结构要支持在任意位置快速插入和删除元素。

对于查找,我们知道Hash表的查找速度是很快的,但是并不满足时序问题。

对于任意位置的插入,以及顺序问题,我们可以想到链表,但是链表的访问并不是随机的,是需要顺序遍历的。

所以,我们得让哈希表和链表结合,形成一个新的数据结构,那就是:哈希链表。
这也就是为什么大部分的LRUCache都是直接基于LinkedHashMap了。
当然,面试的时候肯定不允许直接用LinkedHashMap来做LRUCache。
在这里插入图片描述

借助这个结构,我们再来分析一下上面的三个条件:

1:如果每次默认从链表尾部添加元素,那么显然越靠近尾部的元素就越是最近使用的。越靠近头部的元素就是越久未使用的。
2:对于某一个 key ,可以通过哈希表快速定位到链表中的节点,从而取得对应的 value。
3:链表显示是支持在任意位置快速插入和删除的,修改指针就行。但是单链表无非按照索引快速访问某一个位置的元素,都是需要遍历链表的,所以这里借助哈希表,可以通过 key,快速的映射到任意一个链表节点,然后进行插入和删除。
一、为什么这里要使用双向链表,而不是单向链表?

我们在找到了节点,需要删除节点的时候,如果使用单向链表的话,后驱节点的指针是直接能拿到的,但是这里要求时间复杂度是O(1),要能够直接获取到前驱节点的指针,那么只能使用双向链表。

二、哈希表里面已经保存了 key ,那么链表中为什么还要存储 key 和 value 呢,只存入 value 不就行了?

当我们在删除节点的时候,除了需要删除链表中的节点,还需要删除hash表中的节点,删除哈希表需要知道key,那么这个key从哪里来?那只能从节点里来,所以在节点里key和value都需要存(在删除链表中节点的方法里需要return key,具体见下面的代码)。

代码实现

代码实现

package com.base.learn.cache;

import java.util.HashMap;

/**
 * @author: 张锦标
 * @date: 2023/5/26 12:32
 * LRUCache类
 */
public class LRUCache<V> {
    private HashMap<String,Node<V>> map = new HashMap<>();
    private Integer limit ;
    private Node<V> head;
    private Node<V> end;

    public LRUCache(Integer limit) {
        this.limit = limit;
    }

    public V get(String key){
        //1:从map中获取,如果没有获取到,那么返回null
        Node<V> node = map.get(key);
        if (node==null){
            return null;
        }
        //2:获取到了,需要将当前节点移动到链表尾部
        removeNodeToTail(node);
        return node.value;
    }

    private void removeNodeToTail(Node<V> node) {
        //如果已经是队尾的节点无需移动
        if (node == end) {
            return;
        }
        //先从原位置删掉
        removeNode(node);
        //放到链尾
        addNodeToTail(node);
    }

    /**
     * 将当前节点放入到链表尾部
     * @param node 要放入到链表尾部的节点
     */
    private void addNodeToTail(Node<V> node) {
        if (end != null) {
            end.next = node;
            node.pre = end;
            node.next = null;
        }
        end = node;
        if (head == null) {
            head = node;
        }
    }

    /**
     * 删除链表中的节点
     * @param node 要删除的节点
     * @return 返回被删除的节点对应的key
     */
    private String removeNode(Node<V> node) {
        if (node == head && node == end) {
            //移除唯一的节点
            head = null;
            end = null;
        } else if (node == end) {
            //移除尾节点
            end = end.pre;
            end.next = null;
        } else if (node == head) {
            //移除头节点
            head = head.next;
            head.pre = null;
        } else {
            //移除中间节点
            node.pre.next = node.next;
            node.next.pre = node.pre;
        }
        return node.key;
    }

    public void put(String key,V value){
        Node<V> node = map.get(key);
        if (node != null) {
            //节点已存在更新里面的值
            node.value = value;
            //移动到链尾
            removeNodeToTail(node);
        } else {
            //不存在,首先判断容量,容量满的情况下先删除不常用的,然后插入新节点,容量不满的情况下直接插入
            if (map.size() >= limit) {
                //从链表中移除最不常用的
                String oldKey = removeNode(head);
                //从hashmap中移除
                map.remove(oldKey);
            }
            node = new Node(key, value);
            //添加到链尾
            addNodeToTail(node);
            //添加到hashmap
            map.put(key, node);
        }
    }


    public static void main(String[] args) {
        LRUCache<String> cache = new LRUCache(2);
        cache.put("1", "1");
        cache.put("2", "2");
        System.out.println(cache.get("1"));
        cache.put("3", "3");
        System.out.println(cache.get("2"));
        cache.put("4", "4");
        System.out.println(cache.get("1"));
        System.out.println(cache.get("3"));
        System.out.println(cache.get("4"));
    }
}

class Node<V>{
    public Node pre;
    public Node next;
    public String key;
    public V value;

    public Node(String key, V value) {
        this.key = key;
        this.value = value;
    }

}


在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大环境不好难找工作?三面阿里,幸好做足了准备,已拿offer

大环境不好难找工作&#xff1f;三面阿里&#xff0c;幸好做足了准备&#xff0c;已拿offer 三面大概九十分钟&#xff0c;问的东西很全面&#xff0c;需要做充足准备&#xff0c;就是除了概念以外问的有点懵逼了&#xff08;呜呜呜&#xff09;。回来之后把这些题目做了一个分…

R-Meta分析与【文献计量分析、贝叶斯、机器学习等】多技术融合实践与拓展

Meta分析是针对某一科研问题&#xff0c;根据明确的搜索策略、选择筛选文献标准、采用严格的评价方法&#xff0c;对来源不同的研究成果进行收集、合并及定量统计分析的方法&#xff0c;最早出现于“循证医学”&#xff0c;现已广泛应用于农林生态&#xff0c;资源环境等方面。…

增强型本地文件搜索工具:Find Any File

Find Any File是mac上一款增强型本地文件搜索工具&#xff0c;可以让你在本地磁盘上搜索、查找任何文件&#xff0c;包括本地磁盘的名称、 创建或修改日期、 大小或类型和创建者代码等。小编现为大家提供最新Find Any File Mac破解版&#xff0c;欢迎需要的朋友下载使用。 Find…

屏幕挂灯是不是智商税?明基ScreenBar Halo屏幕挂灯初体验

目录 一、屏幕挂灯是不是智商税&#xff1f;二、文心一言眼里的屏幕挂灯1、明基ScreenBar Halo屏幕挂灯2、屏幕挂灯和普通台灯哪个好&#xff1f; 三、屏幕挂灯初体验四、使用体验五、无线控制器六、专业角度分析1、屏幕工作照明&#xff0c;不是随便一盏灯就可以2、引导光线照…

路径规划算法:基于入侵杂草优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于入侵杂草优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于入侵杂草优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化…

【SA8295P 源码分析】03 - SA8295P QNX Host 上电开机流程分析

【SA8295P 源码分析】03 - SA8295P QNX Host上电开机流程分析 一、阶段1 固件开机自检 (SM BIST)&#xff1a;APPS PBL加载XBL后触发 INT_RESET进行Warm Reset二、阶段2 固件开机自检 (SM BIST)&#xff1a;加载TZ&#xff0c;初始Hypervisor&#xff0c;启动QNX Kernel&#x…

Flume实践

1 NetCat方式 ]# ./bin/flume-ng agent --conf conf--conf-file ./conf/flume_netcat.conf --name a1 -Dflume.root.loggerINFO,console [rootmaster ~]# yum -y intalll telnet 发数据&#xff1a; ]# telnet master 44444 数据接收&#xff0c;是在终端上接收的&#xff0…

从组件化角度聊聊设计工程化

目录 设计系统 设计系统的定义 设计系统的优势 设计系统存在的问题 设计工程化 设计系统探索 设计系统落地实践 Design Token Design Token 实践 设计工程化理想方案构想 展望 参考文献 近几年围绕业务中台化的场景&#xff0c;涌现出了许多低代码平台。面对多组件…

Qt翻金币小游戏详细教程(内涵所有源码、图片资源)

一、项目简介 翻金币项目是一款经典的益智类游戏&#xff0c;我们需要将金币都翻成同色&#xff0c;才视为胜利。首先&#xff0c;开始界面如下&#xff1a; 点击start按钮&#xff0c;进入下层界面&#xff0c;选择关卡&#xff1a; 在这里我们设立了20个关卡供玩家选择&…

Jmeter组件:Random CSV Data Set Config(随机读取文件数据)

一、Jmeter组件&#xff1a;Random CSV Data Set Config(随机读取文件数据) 功能&#xff1a;该组件可以随机读取CSV文件中的每一行的数据 二、下载插件&#xff1a;(jmeter-plugins-random-csv-data-set-xx.jar),并放到lib/ext目录下&#xff0c;重启jmeter 也可以在Jmeter…

Nginx基本使用以及部署前端项目

前言 最近学习了一下Nginx&#xff0c;整理了一个博客&#xff0c;主要参考的是狂神说的b站视频教程&#xff0c;文章链接如下&#xff1a;狂神说Nginx快速入门 一、下载、启动Nginx 1.下载Nginx 到Nginx官方选择自己电脑适用的稳定版本下载&#xff0c;我下载的的windows版…

ChatGPT免费使用的方法有哪些?

目录 一、ChatGpt是什么&#xff1f; 二、ChatGPT国内免费使用的方法&#xff1a; 第一点&#xff1a;电脑端 第二点&#xff1a;手机端 三、结语&#xff1a; 一、ChatGpt是什么&#xff1f; ChatGPt是美国OpenAI [1] 研发的聊天机器人程序 。更是人工智能技术驱动的自然语…

图像算法工程师岗位的主要职责(合集)

图像算法工程师岗位的主要职责 一、确定岗位的职责 1.根据工作任务的需要确立工作岗位名称及其数量; 2.根据岗位工种确定岗位职务范围; 3.根据工种性质确定岗位使用的设备、工具、工作质量和效率; 4.明确岗位环境和确定岗位任职资格; 5.确定各个岗位之间的相互关系; 6.根据岗位…

Solidity基础七

无论风暴将我带到什么岸边&#xff0c;我都将以主人的身份上岸 目录 一、Solidity的单位 1. 货币Ether 2. 时间单位Time 二、地址的形成 三、以太坊的账户 1.内部账户&#xff08;简称CA&#xff09; 2.外部账户&#xff08;简称EOA&#xff09; 3.内部账户和外部账户…

【Linux性能优化】你知道什么是平衡负载么

什么是平衡负载 首先大家思考一下&#xff0c;当你发现自己的服务变慢时&#xff0c;你会首先使用什么命令来排查&#xff1f;我通常做的第一件事&#xff0c;就是执行top或者uptime命令来了解系统的负载情况。比如像下面这样&#xff0c;我在命令行里输入top命令&#xff0c;…

[高光谱]使用PyTorch的dataloader加载高光谱数据

本文实验的部分代码参考 Hyperspectral-Classificationhttps://github.com/eecn/Hyperspectral-Classification如果对dataloader的工作原理不太清楚可以参见 [Pytorch]DataSet和DataLoader逐句详解https://blog.csdn.net/weixin_37878740/article/details/129350390?spm1001…

网络货运平台源码 管理平台端+司机端APP+货主端APP源码

网络货运平台系统源码&#xff0c;网络货运平台源码 管理平台端司机端APP货主端APP 遵循政策要求的八项基本功能&#xff0c;结合货主、实际承运人、监管方等多方业务场景&#xff0c;构建人、车、货、企一体的标准化网络货运平台系统。具有信息发布、线上交易、全程监控、金融…

数据库基础——6.排序与分页

这篇文章来讲一下数据库的排序与分页 目录 1.排序数据 1.1排序规则 1.2 单列排序 1.3 多列排序 2.分页 2.1 背景 2.2 实现规则 2.3 拓展 1.排序数据 1.1排序规则 使用 ORDER BY 子句排序 ASC&#xff08;ascend&#xff09;&#xff1a;升序 &#xff1b; DESC&a…

vue项目中使用depcheck检查缺失的依赖项目

使用depcheck检查缺失的项目依赖 由来&#xff1a;今天在做地铁的时候&#xff0c;刷短视频发现一个非常好用的东西&#xff0c;分享一下 它可以帮助我们找出问题&#xff0c;在 package.json 中&#xff0c;每个依赖包如何被使用、哪些依赖包没有用处、哪些依赖包缺失。它是解…

2023年,推荐10个让你事半功倍的CSS在线生产力工具

1、CSS Gradient CSS Gradient 是一个在线工具&#xff0c;可以帮助用户创建并生成 CSS 渐变代码。用户可以使用该工具中提供的图形用户界面来调整颜色、方向和渐变类型&#xff0c;然后生成相应的 CSS 代码。用户可以将生成的代码复制并粘贴到自己的 CSS 样式表中&#xff0c…