Java——LRUCache

news2024/9/21 4:39:41

概念

简单来说,由于我们的空间是有限的,所以发明了这个数据结构,当我们的空间不够添加新的元素时,就会删除最近最少使用的元素。

其底层逻辑通过哈希表和链表共同实现。哈希表中存储链表的每一个元素,方便进行元素的获取,而链表则是为了方便移动元素,减少时间复杂度。

当一个元素被添加或者被访问时,其会移动到链表的尾部。因此,越靠近链表尾部的元素,越是最近访问次数多的元素,反之,越靠近头部则是最近不访问的。因此当链表的长度达到我们预设的某一个值时,我们只需要将链表的头部的元素删除,再把哈希表中的元素删除即可。

LinkedHashMap

在JDK中,就有类似于LRUCache的数据结构——LinkedHashMap,下面来介绍一下使用方法

其构造方法有三个参数,第一个参数是初始容量,第二个参数是负载因子(HashMap在多大的时候扩容,默认是0.75f)而第三个参数则是一个布尔值。

当第三个参数为false时,我们的链表的顺序就是基于插入的顺序排列的,先插进来的靠近头,后插进来的靠近尾部。当我们删除时,就会直接删除最先插入的节点

而当第三个参数为true时,链表中元素的排序则是基于访问的顺序的,最近访问过的就放到链表的尾部,也就是我们LRUCache的概念,因此我们把这个值设置为true

LinkedHashMap<String, Integer> linkedHashMap = new java.util.LinkedHashMap<>(16,0.7f,true);

使用put可以插入键值对,使用get则可以通过键访问对应的值。可以看到,当我们获取hi对应的值时,我们的链表顺序变成hi这个键值对在最后了


linkedHashMap.put("hello",1);
linkedHashMap.put("hi",2);
linkedHashMap.put("world",1);

System.out.println(linkedHashMap);
System.out.println(linkedHashMap.get("hi"));
System.out.println(linkedHashMap);

在这里插入图片描述

LRUCache

而基于JDK的LinkedHashMap,我们可以实现一个LRUCache

首先继承于LinkedHashMap

public class LRUCache extends LinkedHashMap<Integer,Integer>

定义一个容量,当我们的链表数据大于这个容量的个数,就删除头部的元素

public int capacity;

构造方法通过重写LinkedHashMap进行构造,然后对capacity进行赋值

LRUCache(int capacity){
 //基于访问顺序
    super(capacity,0.75f,true);
    this.capacity = capacity;
}

然后只需要重写put和get方法,使用默认的put和get方法即可

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

@Override
public Integer get(Object key) {
    return super.get(key);
}

最后只需要重写一下其中的removeEldestEntry,这个方法是为真时删除最老的元素,所以我们只需要写当size()大于capacity时即可

@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
    return size() > capacity;
}

验证

public static void main(String[] args) {
    LRUCache lruCache = new LRUCache(3);
    lruCache.put(1,10);
    lruCache.put(2,20);
    lruCache.put(3,30);

    System.out.println(lruCache);
    System.out.println(lruCache.get(2));
    System.out.println(lruCache);

    lruCache.put(4,40);
    System.out.println(lruCache);
}

在这里插入图片描述
可以看到,当我们的容量为3时,添加三个元素后,获取2对应的val,那么2:20这个键值对就会移动到链表的尾部,接着,我们再插入4:40这个键值对,我们的空间不够了,就会删除头部的键值对——3:30

MyLRUCache

我们也可以不借助LinkedHashMap,自己通过链表和HashMap来写一个LRUCache

定义链表中的节点

我们的链表使用的是双向带头带尾链表,因此在定义时需要定义prev和next,并且其中存储的是键值对,因此需要定义key和val

static class DLinkNode {
   public int key;
    public int val;
    public DLinkNode prev;
    public DLinkNode next;

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

    public DLinkNode(){}

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

参数

MyLRUCache中需要存储链表的头尾节点,元素的个数,哈希表和容量

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;
}

put方法

put方法需要先在哈希表中看这个key是否存储过

如果没有存储过,那么需要先创建一个节点,赋值key和val,将这个节点存储在哈希表中,然后将这个节点存储到链表的尾部,然后让usedSize++,判定usedSize是否超过了capacity,如果超过了,那么需要将链表的头节点删除,并且在哈希表中删除,再让usedSize–

而如果存储过了这个值,那么只需要在哈希表中获取这个节点,更新一下key对应的val,然后将这个节点移动到尾部即可。

public void put(int key, int val){
    //查找当前key是否存储过
    DLinkNode node = cache.get(key);
    if(node == null){
        //当前key没有存储
        //实例化一个节点
        DLinkNode dLinkNode = new DLinkNode(key, val);

        //存储到map中
        cache.put(key,dLinkNode);

        //存储到链表的尾巴
        addTail(dLinkNode);
        usedSize++;

        //检查当前的链表数据个数是否达到capacity
        if(usedSize > capacity){
            //链表数据个数超过capacity,移除头部节点
            DLinkNode remNode = removeHead();
            cache.remove(remNode.key);
            usedSize--;
        }

    } else {
        //当前key存储过
        //更新key对应的val
        node.val = val;
        //将该节点移动到尾部
        moveToTail(node);
    }

    printNodes("put");
}

moveToTail

先移除这个节点,然后将这个节点添加到链表尾部

/**
 * 将当前节点移动到尾部
 * @param node
 */
private void moveToTail(DLinkNode node) {
    removeNode(node);
    addTail(node);
}

private void removeNode(DLinkNode node) {
 	node.prev.next = node.next;
    node.next.prev = node.prev;
}

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

removeHead

将节点从头部删除,并返回该节点

public DLinkNode removeHead(){
    DLinkNode del = head.next;
    head.next = del.next;
    del.next.prev = head;
    return del;
}

get方法

get方法则比较简单,拿key到哈希表中找val,如果不存在则直接返回-1,如果存在,那么将这个节点放到链表的尾部,然后返回这个key对应的val

/**
 * 访问key对应的节点
 * @param key
 * @return
 */
public int get(int key){
    DLinkNode dLinkNode = cache.get(key);
    if(dLinkNode == null){
        return -1;
    }
    //将节点放到链表尾部
    moveToTail(dLinkNode);

    printNodes("get");
    return dLinkNode.val;
}

print

这个方法是用来打印链表中所有的节点的

public void printNodes(String str){
    System.out.println(str + ": ");
    DLinkNode dLinkNode = head.next;
    while(dLinkNode != tail){
        System.out.print(dLinkNode);
        dLinkNode = dLinkNode.next;
        System.out.println();
    }
}

测试

还使用上一个测试使用的数据集,可以看到最终的结果是相同的

public static void main(String[] args) {
    MyLRUCache lruCache = new MyLRUCache(3);
    lruCache.put(1,10);
    lruCache.put(2,20);
    lruCache.put(3,30);

    lruCache.get(2);

    lruCache.put(4,40);
}

在这里插入图片描述

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

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

相关文章

Mysql分布式锁(三)悲观锁实现并发

在前面的方法中&#xff0c;一条sql语句中仍然存在着很多问题&#xff0c;于是我们可以用悲观锁来代替解决。 假设我们不用一条sql&#xff0c;仍然用先查询&#xff0c;判断&#xff0c;最后更新来实现业务。 文章目录悲观锁 select...for update1. 不加悲观锁1) 两个机器连接…

因果推断2--深度模型介绍(个人笔记)

目录 一、方法介绍 1.1TarNet 1.1.1TarNet 1.1.2网络结构 1.1.3counterfactual loss 1.1.4代码实现 1.2Dragonet 1.3DRNet 1.4VCNet VCNET AND FUNCTIONAL TARGETED REGULARIZATION FOR LEARNING CAUSAL EFFECTS OF CONTINUOUS TREATMENTS 二、待补充 一、方法介绍 …

AcWing 第82场周赛

AcWing 第82场周赛 竞赛 - AcWing B 4783. 多米诺骨牌 - AcWing题库 模拟题&#xff0c;考察代码描述问题的能力。 由题意所给的数学形式化定义中看出&#xff0c;所给的骨牌初始序列 L 和 R 的顺序一定是相互交错的&#xff0c;即 ...LRLRLRLR... 所以&#xff0c;一旦遇到…

KNN算法 搜索最优超参数:n_neighbors/weights/p

目录 一&#xff1a;遍历参数 超参调优测试 二&#xff1a;网格模型 超参调优测试 三&#xff1a;模型保存 四&#xff1a;模型使用 一&#xff1a;遍历参数 超参调优测试 1.1 超参调试&#xff0c;找到模型最优解[仅做测试&#xff0c;得出最优&#xff1a;n_neighbors, …

PowerDesigner导入SQL脚本生成带中文注释(comment)的ER图并保存为图片格式(含通用可执行vb脚本文件)

目录 1、安装数据库建模工具PowerDesigner 16.5 2、打开 PowerDesigner&#xff0c;选择反向工程 3、选择数据库类型 4、导入SQL脚本文件并生成数据库表模型 5、去掉Diagram画板黑色网格线&#xff08;选做&#xff09; 6、ER图常规显示&#xff08;包含是否为Null及表名…

D. Same Count One(模拟 + 思维转换(行不行,从列入手))

Problem - D - Codeforces ChthollyNotaSeniorious收到了AquaMoon的一份特殊礼物&#xff1a;n个长度为m的二进制数组。AquaMoon告诉他&#xff0c;在一次操作中&#xff0c;他可以选择任何两个数组和1到m中的任何位置&#xff0c;并交换这些数组中位置的元素。 他对这个游戏很…

RabbitMQ知识总结一

更多知识在我的语雀平台&#xff1a; https://www.yuque.com/ambition-bcpii/muziteng RabbitMQ 1. RabbitMQ引言 1.1 什么是MQ MQ&#xff08;Message Queue&#xff09;消息队列&#xff0c;是基础数据结构中“先进先出”的一种数据结构。一般用来解决应用解耦&#xff0…

带token的登陆页面爆破方法(burp宏+爬虫脚本分享)

文章目录前言一、token参数分析二、burp设置宏操作三、爬虫脚本四、小结前言 在工作中&#xff0c;会遇到很多登陆页面有token保护&#xff0c;如果用Burpsuite直接抓取数据包并使用爆破模块&#xff0c;则会因token过期导致无法爆破。此时至少可以采用三种办法&#xff1a; 第…

Java诊断工具——arthas,实时监控,了解一下

文章目录1、arthas 简介官方文档2、arthas 的使用场景3、安装&启动3.1 安装3.2 启动4、常用命令5、使用示例5.1 stack5.2 jad5.3 sc5.4 watch5.5 trace5.6 jobs5.7 logger5.8 dashboard5.9 redefine6、其它1、arthas 简介 arthas是由阿里巴巴中间件团队开源的Java诊断工具。…

kubernetes对外服务之Ingress

目录 ​​​​​​​一、Ingress 是什么 1.1Service的作用 1.2Ingress简介 二、Ingress 安装 三、Ingress 代理访问 3.1Ingress HTTP 代理访问 3.2 Ingress: HTTPS 代理访问 3.3Ingress Contronler怎么工作的&#xff1f; ​​​​​​​​​​​​​​一、Ingress 是什…

Java核心实操:内存溢出 实战、内存泄漏实战

文章很长&#xff0c;而且持续更新&#xff0c;建议收藏起来&#xff0c;慢慢读&#xff01;疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 &#xff1a; 免费赠送 :《尼恩Java面试宝典》 持续更新 史上最全 面试必备 2000页 面试必备 大厂必备 涨薪必备 免费赠送 经典…

ARM系列之MMU TLB和ASID基础概念介绍。

目录1、为什么要设计TLB&#xff1f;TLB中不包含我们需要的映射关系怎么办&#xff1f;2、TLB中都包含了啥&#xff1f;3、那什么是ASIDAddress Space ID&#xff08;ASID&#xff09;4、小结内存寻址简要过程如下&#xff1a;VA以页表大小取余&#xff0c;得到PA的低位&#x…

Go sync.Pool池化的学习

一句话总结&#xff1a;保存和复用临时对象&#xff0c;减少内存分配&#xff0c;降低 GC 压力。 一.前言 Go 语言标准库也大量使用了 sync.Pool&#xff0c;例如 fmt 和 encoding/json。 1.1 要解决的问题 一个新技术亦或是一个新名词&#xff0c;总是为了解决一些问题才出…

数据挖掘课程设计报告总结

一、实验题目 实验一 Apriori算法设计与应用 二、背景介绍 Apriori算法是一种挖掘关联规则的频繁项集算法&#xff0c;其核心思想是通过候选集生成和向下封闭检测两个阶段来挖掘频繁项集。 三、实验内容 1.3.1 运用的理论知识 关联规则挖掘是数据挖掘中最活跃的研究方法之…

数控恒流源电路简单讲解

&#xff08;1&#xff09;最近课设是做一个可步进的恒流源&#xff0c;所以查查找了很多资料之后。说一下自己对于恒流源电路的简单理解。 &#xff08;2&#xff09;我只是会将怎么使用和调整数据进行讲解&#xff0c;至于为什么这样只会讲我懂的部分。本人知道的也不是很多&…

【RPA前置知识】 整理并总结ForEach Activity类

&#x1f40b;作者简介&#xff1a;博主是一位.Net开发者&#xff0c;同时也是RPA和低代码平台的践行者。 &#x1f42c;个人主页&#xff1a;会敲键盘的肘子 &#x1f430;系列专栏&#xff1a;.Net实用方法总结 &#x1f980;专栏简介&#xff1a;本专栏介绍如何编写 Windows…

Biopython教程

Biopython教程 参考&#xff1a; https://biopython-cn.readthedocs.io/zh_CN/latest/index.html 蛋白质文件获取 Entrez方法 from Bio import Entrez Entrez.email邮箱名 #如123456789qq.com handleEntrez.esearch(dbprotein,term2rbg) recordEntrez.read(handle) idrecor…

C++PrimerPlus 第八章 函数探幽-8.2 引用变量

目录 8.2 引用变量 8.2.1 创建引用变量 8.2.2 将引用用作函数参数 8.2.3 引用的属性和特别之处 8.2.3.1 临时变量、引用参数和const 8.2.4 将引用用于结构 8.2.4.1 程序说明 8.2.4.2 为何要返回引用 8.2.4.3 返回引用时需要注意的问题 8.2.4.4 为何将const用于引用返…

纳米柱阵列超颖表面构建模块的严格分析

摘要 利用先进的制造技术&#xff0c;人们成功实现了具有高数值孔径的可见波长的超透镜。通常使用空间变化的纳米结构作为模块来构建超透镜。在这个例子中分析了用于组成偏振不敏感超透镜的纳米柱状结构。利用傅立叶模态方法&#xff08;FMM&#xff0c;也称为RCWA&#xff09;…

Windows配置开机自启jar包,不显示黑窗口,并输出日志

背景 如果是在 Linux 下开机自启一个服务相对比较简单&#xff0c;这次遇到一个需求是关于 Windows 开机自启的&#xff1a; 在 Windows 环境下开机自动运行一个 SpringBoot 服务&#xff1b;而且由于是一个后台服务&#xff0c;要求对终端用户无感知&#xff1b;为后期维护方…