LinkedHashMap实现LRU

LRU

环境:JDK11

最近接触LRU(Least Recently Used),即最近最少使用,也称淘汰算法,在JDK中LinkedHashMap有相关实现

LRU的LinkedHashMap实现

LinkedHashMap继承HashMap。所以内存的存储结构和HashMap一样,但是LinkedHashMapHashMap多一个headtail,这是一个双向链表,head表示最早添加进Map中的元素,也就是最老的数据,tail就是最后加进来的元素,当满足某种条件后,就会自动删除head节点。LRU就是这样的。

transient LinkedHashMap.Entry<K,V> head;transient LinkedHashMap.Entry<K,V> tail;

添加操作

public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}

put方法中会创建一个Node节点,LinkedHashMap重写了newNode方法,创建的node是LinkedHashMap.Entry

Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {LinkedHashMap.Entry<K,V> p =new LinkedHashMap.Entry<>(hash, key, value, e);// 使用head和tail组成链表linkNodeLast(p);return p;
}
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {LinkedHashMap.Entry<K,V> last = tail;tail = p;if (last == null)head = p;else {p.before = last;last.after = p;}
}

在这里插入图片描述

put方法结束后还会调用afterNodeInsertion方法,一个空实现的方法,该方法在LinkedHashMap中实现了

//evict: 是否在插入后进行驱逐操作,比如LinkedHashMap实现颗LRU算法,会把老的数据删除
void afterNodeInsertion(boolean evict) { }
void afterNodeInsertion(boolean evict) { // possibly remove eldestLinkedHashMap.Entry<K,V> first;if (evict && (first = head) != null && removeEldestEntry(first)) {// 删除最老的数据K key = first.key;removeNode(hash(key), key, null, false, true);}
}

LinkedHashMapremoveEldestEntry方法默认返回false。removeEldestEntry方法表示达到一定条件后,LinkedHashMap就会自动删除最老的数据,比如当缓存容量达到一定值之后,需要把最不经常使用的缓存删掉或者保存到磁盘中。所以使用LinkedHashMap来实现LRU算法,记得继承LinkedHashMap重写removeEldestEntry方法

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {return false;
}

比如我们定义一个LRULinkedHashMap类,继承LinkedHashMap,重写removeEldestEntry方法,记得把accessOrder设置为true。

public class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> {private int threshold;public LRULinkedHashMap(int threshold) {super(16, 0.75f, true);this.threshold = threshold;}@Overrideprotected boolean removeEldestEntry(Map.Entry eldest) {return size() > threshold;}
}

取出操作

accessOrder为true,取出操作,就会影响节点在head和tail组成的链表中的位置,即访问次数越多,数据越活跃。

accessOrder为false,就跟HashMap一样了

public V get(Object key) {Node<K,V> e;if ((e = getNode(hash(key), key)) == null)return null;if (accessOrder)afterNodeAccess(e);return e.value;
}

afterNodeAccess方法就是将最活跃的节点放到tail

void afterNodeAccess(Node<K,V> e) { // move node to last// 最新的数据,或者叫最活跃的数据LinkedHashMap.Entry<K,V> last;// 将节点e变成tail,tail表示目前最活跃的数据节点// 如果e已经是tail了,即已经是最活跃的节点了,就不要处理了if (accessOrder && (last = tail) != e) {LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;p.after = null;if (b == null)head = a;elseb.after = a;if (a != null)a.before = b;elselast = b;if (last == null)head = p;else {p.before = last;last.after = p;}tail = p;++modCount;}
}

删除操作

使用remove方法删除节点后,会调用afterNodeRemoval方法,HashMap对此方法是空实现,LinkedHashMap重写了这个方法,就是将e从链表中删除

void afterNodeRemoval(Node<K,V> e) { // unlinkLinkedHashMap.Entry<K,V> p =(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;p.before = p.after = null;if (b == null)head = a;elseb.after = a;if (a == null)tail = b;elsea.before = b;
}

遍历LinkedHashMap

这是题外话了,来都来了,就讲讲吧

HashMap在遍历的时候,是乱序的,即不是按照添加的顺序遍历。LinkedHashMap可以顺序遍历,我们看看怎么实现的。

map.forEach((k, v) -> {System.out.print(k + ":" + v + "  ");
});
public void forEach(BiConsumer<? super K, ? super V> action) {if (action == null)throw new NullPointerException();int mc = modCount;// 实际是在遍历这个链表for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)action.accept(e.key, e.value);if (modCount != mc)// 遍历的过程中不能修改LinkedHashMapthrow new ConcurrentModificationException();
}

可以使用iterator遍历,中途可以删除节点iterator.remove();

Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();while (iterator.hasNext()){Map.Entry<String, Integer> next = iterator.next();String key = next.getKey();Integer value = next.getValue();// 使用iterator的remove方法删除节点没事iterator.remove();System.out.print(key + ":" + value+ "  ");
}

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

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

相关文章

基于rk356x u-boot版本功能分析及编译相关(三)Makefile分析

🎏技术驱动源于热爱,祝各位学有所成。 文章目录 一、Makefile简要概述二、简要流程图三、Makefile文件具体分析大家好哈,这次因工作比较忙,文章更新拖的有些久了。哈哈,话不多说,咱们接着上次继续说u-boot的Makefile。 一、Makefile简要概述 一般要了解u-boot源码的编译…

shell(1)脚本创建执行与变量使用

shell&#xff08;1&#xff09;脚本创建执行与变量使用 声明&#xff01; 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章 笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&…

第5章总体设计-5.4 硬件可行性分析

5.4 硬件可行性分析 5.4.1 硬件方案评估1. 框式产品硬件可行性分析&#xff08;1&#xff09;机框设计可行性。&#xff08;2&#xff09;单板设计可行性。&#xff08;3&#xff09;核心功能器件选型。&#xff08;4&#xff09;数据流。 2. 盒式产品硬件可行性分析3. 终端产品…

TOIS24|推荐公平性的反事实解释

论文&#xff1a;https://arxiv.org/pdf/2307.04386 代码&#xff1a;https://anonymous.4open.science/r/CFairER-anony/. 关键词&#xff1a;可解释推荐;公平;反事实的解释;强化学习 1 动机 现有推荐系统存在的公平性问题&#xff0c;例如性别歧视和种族偏见等&#xff0c;…

week 3 - Assembly Language

Important Instructions and Syntax 此内容是以MASM编写的&#xff0c;你将使用Visual C/C内联汇编来编程&#xff0c;因此数据元素的声明有所不同&#xff0c;但概念和指令集&#xff08;instruction sets)相同。 一、General-Purpose Registers 寄存器是CPU内的命名存储单元…

6.C操作符详解,深入探索操作符与字符串处理

C操作符详解&#xff0c;深入探索操作符与字符串处理 C语言往期系列文章目录 往期回顾&#xff1a; C语言是什么&#xff1f;编程界的‘常青树’&#xff0c;它的辉煌你不可不知VS 2022 社区版C语言的安装教程&#xff0c;不要再卡在下载0B/s啦C语言入门&#xff1a;解锁基础…

校园导航系统

关于数据结构的一个整理&#xff1a; 1、链式有序表的合并 2、栈 3、队列 4、二叉树、哈夫曼报文 5、图论 6、十大排序 7、校园导航系统 文章目录 校园导航系统演示示例代码示例1、弧结点和顶点节点2、Map节点3、用户 校园导航系统 采用C语言涉及了数据库相关的操作&am…

食品进出库库存管理发货开单软件下载 佳易王食品进出库管理系统操作教程

一、概述 【软件资源下载在文章最后】 食品进出库库存管理发货开单软件下载 食品进出库管理系统操作教程 商品进出库管理软件是一款操作简便的进出库管理软件&#xff0c;管理入库&#xff0c;出库&#xff0c;库存&#xff0c;同时打印发货单。 二、软件操作教程 第一步&a…

C++和OpenGL实现3D游戏编程【连载18】——加载OBJ三维模型

1、本节课要实现的内容 以前我们加载过立方体木箱,立方体的顶点数据都是在程序运行时临时定义的。但后期如果模型数量增多,模型逐步复杂,我们就必须加载外部模型文件。这节课我们就先了解一下加载OBJ模型文件的方法,这样可以让编程和设计进行分工合作,极大丰富我们游戏效…

二刷代码随想录第四天

24. 两两交换链表中的节点 设置个虚拟头节点画图理清楚节点之间的指向关系 class Solution { public:ListNode* swapPairs(ListNode* head) {ListNode* dummyHead new ListNode(0);dummyHead->next head;ListNode* cur dummyHead;while (cur->next ! nullptr &&…

【Linux】proc 文件系统详解

/proc 文件系统是 Linux 内核提供的一种特殊的文件系统&#xff0c;它主要用于显示内核和进程的信息。/proc 文件系统是一个虚拟文件系统&#xff0c;这意味着它并不占用实际的磁盘空间&#xff0c;而是由内核动态生成的内容。通过 /proc 文件系统&#xff0c;用户可以读取系统…

Linux文件系统

Linux文件系统 Linux 文件系统是 Linux 操作系统中用于存储和组织文件的结构。以下是一些关键概念和常见的 Linux 文件系统类型&#xff1a; 关键概念 文件系统层次结构&#xff1a;Linux 使用统一的文件系统层次结构&#xff0c;所有文件和目录都从根目录 / 开始。 目录结构…

[1.15.X-1.18.X]Herobrine-吾王HIM插件

Herobrine 这款插件99%自定义!为你的服务器增加一个吓人的HIM&#xff0c;该插件是一个非玩家角色&#xff0c;由 Minecraft 的粉丝创建。从来没有真正成为 Minecraft 游戏的一部分&#xff0c;这个故事是他在 Minecraft 世界里出没&#xff0c;Mojang 通过开玩笑地将“移除的 …

CTF 取证技术

01 流量分析 筛选器的使用 追踪流 文件导出 实例&#xff1a;通过筛选 http ,推断出 攻击者很可能 是 执行一个 文件上传 的攻击hack.php 很可能就是 攻击者 上传的 webshell依次进行 http 的 追踪流 查看查看到最后&#xff0c;发现响应中 有 PK文件头的存在 &#xff0c;说…

【GPIO】3.上/下 拉电阻通讯中的作用

一.什么是上/下拉电阻 上拉、下拉电阻统一称为拉电阻&#xff0c;作用是将状态不确定的信号线通过一个电阻将其箝位至高电平&#xff08;上拉&#xff09;或低电平&#xff08;下拉&#xff09; 这里有人可能会疑惑&#xff1f; 什么叫状态不确定的信号&#xff1f; 在数字电…

分享购:前期布局与后期问题解决策略

在当今电商与消费模式不断创新的时代&#xff0c;分享购作为一种极具潜力的商业模式&#xff0c;正受到越来越多的关注。然而&#xff0c;要想让分享购真正发挥优势、实现可持续发展&#xff0c;无论是前期的精心布局&#xff0c;还是后期妥善应对各类问题&#xff0c;都至关重…

51c大模型~合集46

我自己的原文哦~ https://blog.51cto.com/whaosoft/11908179 #HITS 北大李戈团队提出大模型单测生成新方法&#xff0c;显著提升代码测试覆盖率 单元测试是软件开发流程中的一个关键环节&#xff0c;主要用于验证软件中的最小可测试单元&#xff0c;函数或模块是否按预期工作…

中断与异常处理:走进代码

在操作系统的核心部分&#xff0c;中断&#xff08;Interrupt&#xff09;和异常&#xff08;Exception&#xff09;的处理机制是不可或缺的基础。它们的设计决定了系统的响应能力、稳定性和可扩展性。本文将深入探讨 Linux 内核中的中断与异常处理机制&#xff0c;并结合更多实…

智慧社区管理系统平台全面提升物业管理效率与用户体验

内容概要 随着科技的发展&#xff0c;智慧社区管理系统平台应运而生&#xff0c;成为现代物业管理的重要工具。这个平台通过整合多种先进的管理手段&#xff0c;为物业服务提供了全新的解决方案。智慧社区管理系统的核心在于其高效、便捷、智能的特点&#xff0c;最大程度地提…

Pytest-Bdd-Playwright 系列教程(9):使用 数据表(DataTable 参数) 来传递参数

Pytest-Bdd-Playwright 系列教程&#xff08;9&#xff09;&#xff1a;使用 数据表&#xff08;DataTable 参数&#xff09; 来传递参数 前言一、什么是 datatable 参数&#xff1f;Gherkin 表格示例 二、datatable 参数的基本使用三、完整代码和运行效果完整的测试代码 前言 …