【设计并实现一个满足 LRU (最近最少使用) 缓存约束的数据结构】

news2024/11/18 10:44:20

文章目录

  • 一、什么是LRU?
  • 二、LinkedHashMap 实现LRU缓存
  • 三、手写LRU


一、什么是LRU?

LRU是Least Recently Used的缩写,意为最近最少使用。它是一种缓存淘汰策略,用于在缓存满时确定要被替换的数据块。LRU算法认为,最近被访问的数据在将来被访问的概率更高,因此它会优先淘汰最近最少被使用的数据块,以给新的数据块腾出空间。

如图所示:

在这里插入图片描述

  1. 先来3个元素进入该队列
    在这里插入图片描述

  2. 此时来了新的元素,因为此时队列中每个元素的使用的次数都相同(都是1),所以会按照LFU的策略淘汰(即淘汰掉最老的那个)
    在这里插入图片描述

  3. 此时又来了新的元素,而且是队列是已经存在的,就会将该元素调整为最新的位置。
    在这里插入图片描述

  4. 如果此时又来了新的元素,还是”咯咯“,由于”咯咯“已经处于最新的位置,所以大家位置都不变。
    在这里插入图片描述

  5. 同理,一直进行上述的循环


二、LinkedHashMap 实现LRU缓存

以力扣的算法题为例子:


力扣146. LRU 缓存


在 jdk 官方的介绍中可以看出,该数据结构天生适合实现 LRU。

在这里插入图片描述

代码示例一:


/**
 * 利用继承 LinkedHashMap 的方式实现LRU缓存
 */


class LRUCache extends LinkedHashMap<Integer, Integer> {
	// 缓存容量
    private int capacity;

    public LRUCache(int capacity) {
    	// 初始化
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }
    
    public int get(int key) {
        return super.getOrDefault(key, -1);
    }
    
    public void put(int key, int value) {
        super.put(key, value);
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
    	// 重写比较方法
        return super.size() > capacity;
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

代码示例二:

/**
 * 利用 LinkedHashMap 特点的方式实现LRU缓存
 */
 
class LRUCache {
   // 额定容量
    private final int CAPACITY;
    // 使用 LinkedHashMap 的有序排重特点达到要求
    private final Map<Integer, Integer> map;


    public LRUCache(int capacity) {
        // 初始化
        CAPACITY = capacity;
        map = new LinkedHashMap<>(CAPACITY);
    }

    public int get(int key) {
        Integer value = map.get(key);
        if (value != null) {// 存在
            // 1. 先删除旧的
            map.remove(key);
            // 2. 再添加回去,同时更新了 key 的位置和 value
            map.put(key, value);
            // 3. 返回结果
            return value;
        } else {// 不存在
            return -1;
        }
    }

    public void put(int key, int value) {
        if (map.size() == CAPACITY) {// 达到最大容量
            if (!map.containsKey(key)) {// 不存在,就删除头
                Integer head = map.keySet().iterator().next();
                map.remove(head);
            } else {// 存在,就删除自身
                map.remove(key);
            }
        } else {// 还有剩余空间,删除自身
            map.remove(key);
        }
        // 添加新的或 key 更新后的 value
        map.put(key, value);
    }
}



三、手写LRU

代码示例如下:

/**
 * https://leetcode.cn/problems/lru-cache/description/
 * 手写 LRU 缓存
 * Map + 双向链表
 */
class LRUCache {
    // Map 负责查找,构建一个虚拟的双向链表,它里面安装的就是一个个 Node 节点,作为数据载体。

    // 参考 HashMap 中的存储方式,使用内部 Node 维护双向链表
    // 1. 构造 Node 节点作为数据载体
    public static class Node<K, V> {
        public K key;
        public V value;
        public Node<K, V> prev;
        public Node<K, V> next;

        public Node() {
            this.prev = this.next = null;
        }

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
            this.prev = this.next = null;
        }
    }

    // 2. 构造一个双向队列,里面存放着 Node 节点
    // 队头元素最新,队尾元素最旧
    static class DoubleLinkedList<K, V> {
        Node<K, V> head;
        Node<K, V> tail;

        // 2.1 构造方法
        public DoubleLinkedList() {
            head = new Node<>();
            tail = new Node<>();
            // 头尾相连
            head.next = tail;
            tail.prev = head;
        }

        // 2.2 添加到头(头插)
        public void addHead(Node<K, V> node) {
            node.next = head.next;
            node.prev = head;
            head.next.prev = node;
            head.next = node;
        }

        // 2.3 删除节点
        public void removeNode(Node<K, V> node) {
            node.next.prev = node.prev;
            node.prev.next = node.next;
            node.next = null;
            node.prev = null;
        }

        // 2.4 获取最后一个节点
        public Node getLast() {
            return tail.prev;
        }
    }

    private final int cacheSize;
    Map<Integer, Node<Integer, Integer>> map;// Map
    DoubleLinkedList<Integer, Integer> doubleLinkedList;// 双向链表


    public LRUCache(int capacity) {
        this.cacheSize = capacity;
        map = new HashMap<>();
        doubleLinkedList = new DoubleLinkedList<>();
    }
    
    public int get(int key) {
        if (map.containsKey(key)) {
            // 命中,更新双向链表
            Node<Integer, Integer> node = map.get(key);
            // 先删除双向链表中的节点
            doubleLinkedList.removeNode(node);
            // 再添加到头部
            doubleLinkedList.addHead(node);
            return node.value;
        } else {
            // 未命中,返回 -1
            return -1;
        }
    }
    
    public void put(int key, int value) {
        if (map.containsKey(key)) {
            // 更新双向链表
            Node<Integer, Integer> node = map.get(key);
            // 新值替换老值,再放回
            node.value = value;
            map.put(key, node);
            // 先删除双向链表中的节点
            doubleLinkedList.removeNode(node);
            // 再添加到头部
            doubleLinkedList.addHead(node);
        } else {
            if (map.size() == cacheSize) {
                // 超出容量,删除双向链表的最后一个节点
                Node lastNode = doubleLinkedList.getLast();
                map.remove(lastNode.key);
                doubleLinkedList.removeNode(lastNode);
            }
            // 新增节点
            Node<Integer, Integer> newNode = new Node<>(key, value);
            map.put(key, newNode);
            doubleLinkedList.addHead(newNode);
        }
    }
}


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

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

相关文章

MySQL两个表的亲密接触-连接查询的原理

MySQL对于被驱动表的关联字段没索引的关联查询&#xff0c;一般都会使用 BNL 算法。如果有索引一般选择 NLJ 算法&#xff0c;有 索引的情况下 NLJ 算法比 BNL算法性能更高。 关系型数据库还有一个重要的概念&#xff1a;Join&#xff08;连接&#xff09;。使用Join有好处&…

Ubuntu22.04报错:ValueError: the symlink /usr/bin/python3 does not point to ...

目录 一、背景 二、如何解决呢&#xff1f; 三、解决步骤 1. 确定可用的 Python 版本 2. 重新设置符号链接 3. 选择默认版本 4. 验证&#xff1a; 四、update-alternatives 详解 1. 命令语法 2. 常用选项 --install添加备选项。 --config&#xff1a;选择默认版本。 …

Linux系统常用命令行指令

Linux系统是一种常用于开源项目开发的生产环境&#xff0c;因其免费、开源、安全、稳定的特点被广泛应用于手机、平板电脑、路由器、电视和电子游戏机等嵌入式系统中&#xff0c;能够更加简便地让用户知道系统是怎样工作的。前几日我安装好了Red Hat Enterprise Linux 9.0&…

新书速览|小学生C++创意编程(视频教学版)

本书让入门C变得轻松易懂&#xff0c;逐步入学。学习一步一个台阶&#xff0c;让孩子不会被其中的难度而吓退。 本书内容 C是信息学奥赛指定的编程语言。本书以通俗易懂的方式深入浅出地介绍了C编程语言&#xff0c;适合作为小学生学习的教材类读物。 《小学生C创意编程&#…

uniapp css样式穿透

目录 前言css样式穿透方法不加css样式穿透的代码加css样式穿透的代码不加css样式穿透的代码 与 加css样式穿透的代码 的差别参考 前言 略 css样式穿透方法 使用 /deep/ 进行css样式穿透 不加css样式穿透的代码 <style>div {background-color: #ddd;} </style>…

opencv#33 边缘检测

边缘检测原理 图像的每一行每一列都可以看成是一个连续的信号经过离散后得到的数值&#xff0c;例如上图左侧给出的图像由黑色到白色的一个信号&#xff0c;也就是图像中某一行像素变化是由黑色逐渐到白色&#xff0c;我们将其对应在一个坐标轴中&#xff0c;将像素值的大小对应…

Linux下安装 Redis7

Linux下安装 Redis7 三、Linux下安装 Redis7【redis-7.2.4.tar.gz】3.1.下载redis的安装包3.1.1.手动下载Redis压缩包并上传【redis-7.2.4.tar.gz】3.1.2.wget工具下载redis-7.2.4.tar.gz 3.2.将安装包进行解压缩3.3.进入redis的安装包3.4.检查是否有gcc 环境3.5.编译和安装并指…

Java基础数据结构之排序

一.排序 1.什么是稳定性 假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记录&#xff0c;若经过排序&#xff0c;这些记录的相对次序保持 不变&#xff0c;即在原序列中&#xff0c; r[i]r[j] &#xff0c;且 r[i] 在 r[j] 之前&#xff0c;而在排序后的序…

Oladance、韶音、南卡开放式耳机,究竟谁在性能与音质上达到巅峰?

​开放式耳机作为新型耳机&#xff0c;以其开放性设计、舒适性和音质表现受到用户青睐。然而&#xff0c;随着市场的繁荣&#xff0c;一些品牌为谋取暴利可能采用劣质材料、不合规的工艺标准制作产品&#xff0c;导致耳机做工质量差&#xff0c;音质差。作为资深音频测评师&…

C++入门语法———命名空间,缺省参数,重载函数

文章目录 一.命名空间1.存在意义2.语法使用1.定义命名空间2.使用命名空间的三种方式 二.缺省参数1.全缺省参数2.半缺省参数 三.重载函数1.定义2.重载原理———名字修饰 一.命名空间 1.存在意义 C命名空间的主要意义是为了避免命名冲突&#xff0c;尤其是在大型项目中可能存在…

图卷积网络(GCN)

本文主要分为两部分&#xff0c;第一部分介绍什么是GCN&#xff0c;第二部分将进行详细的数学推导。 一、什么是GCN 1、GCN 概述 本文讲的GCN 来源于论文&#xff1a;SEMI-SUPERVISED CLASSIFICATION WITH GRAPH CONVOLUTIONAL NETWORKS&#xff0c;这是在GCN领域最经典的论文…

【Java并发】聊聊活锁

在并发编程中&#xff0c;为了保证数据安全性&#xff0c;所以使用锁机制&#xff0c;syn lock cas 等方式保证&#xff0c;但是也从一定程度降低了性能。而除了这个方面&#xff0c;还引入了锁竞争&#xff0c;比如死锁、活锁。 【Java并发】聊聊死锁 避免死锁&#xff1a;避…

动静态库的理解、制作、使用。

一.动静态库的理解。 1.什么是库&#xff1f; 代码是无穷无尽的&#xff0c;当程序猿在写一些项目时&#xff0c;未必所有代码亲历亲为&#xff0c;他们可以在网上寻找大佬写过的一些有关需求的代码&#xff0c;这些代码可以让他们拿过来直接使用&#xff0c;而省去了许多精力…

Linux中文件属性的获取(stat、chmod、Istat、fstat函数的使用)

修改文件权限 函数如下&#xff1a; chmod/fchmod函数用来修改文件的访问权限: #include <sys/stat.h> int chmod(const char *path, mode_t mode); int fchmod(int fd, mode_t mode); 成功时返回0&#xff1b;出错时返回EOF 注意&#xff1a;在vmware和windows共享的文…

K8S的HPA

horiztal Pod Autoscaling&#xff1a;pod的水平自动伸缩&#xff0c;这是k8s自带的模块&#xff0c;它是根据Pod占用cpu比率到达一定的阀值&#xff0c;会触发伸缩机制 Replication controller 副本控制器&#xff1a;控制pod的副本数 Deployment controller 节点控制器&…

c++ mysql数据库编程(linux系统)

ubuntu下mysql数据库的安装 ubuntu安装mysql&#xff08;图文详解&#xff09;-CSDN博客https://blog.csdn.net/qq_58158950/article/details/135667062?spm1001.2014.3001.5501 项目目录结构 数据库及表结构 public.h //打印错误信息 #ifndef PUBLIC_h #define PUBLIC_H…

BIGVGAN: A UNIVERSAL NEURAL VOCODER WITHLARGE-SCALE TRAINING——TTS论文阅读

笔记地址&#xff1a;https://flowus.cn/share/a16a61b3-fcd0-4e0e-be5a-22ba641c6792 【FlowUs 息流】Bigvgan 论文地址&#xff1a; BigVGAN: A Universal Neural Vocoder with Large-Scale Training Abstract 背景&#xff1a; 最近基于生成对抗网络&#xff08;GAN&am…

JavaScript 执行上下文与作用域

执行上下文与作用域 ​ 执行上下文的概念在 JavaScript 中是颇为重要的。变量或函数的上下文决定了它们可以访问哪些数据&#xff0c;以及它们的行为。每个上下文都有一个关联的变量对象&#xff08;variable object&#xff09;&#xff0c; 而这个上下文中定义的所有变量和函…

Vue基础–列表渲染-key的原理

一、v-for列表渲染 1.列表渲染 在真实开发中&#xff0c;我们往往会从服务器拿到一组数据&#xff0c;并且需要对其进行渲染。 这个时候我们可以使用v-for来完成&#xff1b; v-for类似于JavaScript的for循环&#xff0c;可以用于遍历一组数据&#xff1b; 2.v-for基本使用…

QT发送request请求

时间记录&#xff1a;2024/1/23 一、使用步骤 &#xff08;1&#xff09;pro文件中添加network模块 &#xff08;2&#xff09;创建QNetworkAccessManager网络管理类对象 &#xff08;3&#xff09;创建QNetworkRequest网络请求对象&#xff0c;使用setUrl方法设置请求url&am…