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.mzph.cn/diannao/60890.shtml

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

相关文章

IDEA部署AI代写插件

前言 Hello大家好&#xff0c;当下是AI盛行的时代&#xff0c;好多好多东西在AI大模型的趋势下都变得非常的简单。 比如之前想画一幅风景画得先去采风&#xff0c;然后写实什么的&#xff0c;现在你只需描述出你想要的效果AI就能够根据你的描述在几分钟之内画出一幅你想要的风景…

27-压力测试

测试目标 & 测试数据 ● 测试目标 ○ 测试集群的读写性能 / 做集群容量规划 ○ 对 ES 配置参数进行修改&#xff0c;评估优化效果 ○ 修改 Mapping 和 Setting&#xff0c;对数据建模进行优化&#xff0c;并测试评估性能改进 ○ 测试 ES 新版本&#xff0c;结合实际场…

正则表达式常用字符

基础正则 ^:开头字符 $:结尾字符 ^$:空行 .:任意一个字符 *:前一个字符连续出现0次或以上 .*:所有 []&#xff1a;括号中的任意一个字符 [a-z] [a-zA-Z0-9] [a-zA-Z0-9] [^]:除括号内以外的字符 扩展正则 |:或 ssh|telnet|http ():表示整体 ^(ssh|telnet|http)^ssh|^telnet|^ht…

4. Spring Cloud Ribbon 实现“负载均衡”的详细配置说明

4. Spring Cloud Ribbon 实现“负载均衡”的详细配置说明 文章目录 4. Spring Cloud Ribbon 实现“负载均衡”的详细配置说明前言1. Ribbon 介绍1.1 LB(Load Balance 负载均衡) 2. Ribbon 原理2.2 Ribbon 机制 3. Spring Cloud Ribbon 实现负载均衡算法-应用实例4. 总结&#x…

Redis 线程模型详解:理解 Redis 高效性能的关键

Redis 是一个开源的高性能键值存储系统&#xff0c;因其卓越的速度和强大的功能被广泛应用于各种场景&#xff0c;如缓存、消息队列和实时数据存储等。Redis 的性能优越不仅归功于其高效的数据结构和内存存储&#xff0c;还源于其独特的线程模型。本文将详细介绍 Redis 的线程模…

vue3【实战】切换全屏【组件封装】FullScreen.vue

效果预览 原理解析 使用 vueUse 里的 useFullscreen() 实现 代码实现 技术方案 vue3 vite UnoCSS vueUse 组件封装 src/components/FullScreen.vue <template><component:is"tag"click"toggle":class"[!isFullscreen ? i-ep:full-sc…

docker:基于Dockerfile镜像制作完整案例

目录 摘要目录结构介绍起始目录package目录target目录sh目录init.sh脚本start.sh脚本stop.sh脚本restart.sh脚本 config目录 步骤1、编写dockerfilescript.sh脚本 2、构件镜像查看镜像 3、保存镜像到本地服务器4、复制镜像文件到指定目录&#xff0c;并执行init.sh脚本5、查看挂…

lua实现雪花算法

lua实现雪花算法 雪花算法介绍组成部分优点缺点 代码示例 雪花算法介绍 雪花算法&#xff08;Snowflake Algorithm&#xff09;是一种用于生成唯一ID的分布式生成算法&#xff0c;最初由Twitter开发。它的主要目的是在分布式系统中生成唯一的、时间有序的ID&#xff0c;这些ID通…

Spring Boot之Spring-devtools热部署

1、导包 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope> </dependency>2、添加配置 #开启热部署 spring.devtools.restart.enabledtrue #热…

STM32 | 超声波避障小车

超声波避障小车 一、项目背题 由于超声波测距是一种非接触检测技术&#xff0c;不受光线、被测对象颜色等的影响&#xff0c;较其它仪器更卫生&#xff0c;更耐潮湿、粉尘、高温、腐蚀气体等恶劣环境&#xff0c;具有少维护、不污染、高可靠、长寿命等特点。因此可广泛应用于…

第6章:TDengine 标签索引和删除数据

TDengine 标签索引和删除数据 目标 掌握标签索引的创建、删除掌握超表、子表创建以及数据删除删除数据 删除数据是 TDengine 提供的根据指定时间段删除指定表或超级表中数据记录的功能,方便用户清理由于设备故障等原因产生的异常数据。 注意:删除数据并不会立即释放该表所…

微澜:用 OceanBase 搭建基于知识图谱的实时资讯流的应用实践

本文作者&#xff1a; 北京深鉴智源科技有限公司架构师 郑荣凯 本文整理自北京深鉴智源科技有限公司架构师郑荣凯&#xff0c;在《深入浅出 OceanBase 第四期》的分享。 知识图谱是一项综合性的系统工程&#xff0c;需要在在各种应用场景中向用户展示经过分页的一度关系。 微…

多轮对话中让AI保持长期记忆的8种优化方式篇

多轮对话中让AI保持长期记忆的8种优化方式篇 一、前言 在基于大模型的 Agent 中&#xff0c;长期记忆的状态维护至关重要&#xff0c;在 OpenAI AI 应用研究主管 Lilian Weng 的博客《基于大模型的 Agent 构成》[1]中&#xff0c;将记忆视为关键的组件之一&#xff0c;下面我…

消息中间件分类

消息中间件&#xff08;Message Middleware&#xff09;是一种在分布式系统中实现跨平台、跨应用通信的软件架构。它基于消息传递机制&#xff0c;允许不同系统、不同编程语言的应用之间进行异步通信。 常见的消息中间件类型包括&#xff1a; 1. JMS&#xff08;Java Message S…

aws-athena查询语句总结

完全归于本人mysql语句小白&#xff0c;是一点也写不出来&#xff0c;故汇总到此 1. cloudtrail ## 查询事件排序 SELECT eventname,eventtime,count(eventname) as num FROM your_athena_tablename where eventtime between 2024-11-10 and 2024-11-11 group by eventname…

Swift的可选绑定(Optional binding)

在Swift中&#xff0c;有一种变量称为可选变量&#xff08;Optional&#xff09;&#xff0c;具体说明见Swift初步入门。这种变量的值可以存在也可以为空&#xff08;nil&#xff09;。在Swift中&#xff0c;可以通过将if语句和赋值语句结合&#xff0c;有条件地展开&#xff0…

关键JavaScript进行表单验证:提升用户体验与数据完整性

关键JavaScript进行表单验证&#xff1a;提升用户体验与数据完整性 在网页开发中&#xff0c;表单验证是确保用户输入有效数据的重要步骤。有效的表单验证不仅可以提高用户体验&#xff0c;还可以减少服务器端处理无效或错误数据的负担。本文将通过一个简单的示例&#xff0c;…

java集合—List常用的方法

Java集合中的List是一种有序的集合&#xff0c;可以通过索引访问元素。以下是List常用的方法&#xff1a; 添加元素&#xff1a; add(E element)&#xff1a;将指定的元素追加到列表的末尾。add(int index, E element)&#xff1a;将指定的元素插入到列表的指定位置。 获取元…

3D Gaussian Splatting 代码层理解之Part3

最后,内容到达了高斯泼溅过程中最有趣的阶段:渲染!这一步可以说是最关键的,因为它决定了模型的真实性。然而,它也可能是最简单的。在本系列的Part 1和Part2,文章演示了如何将 Raw 3D椭球 转换为可渲染的格式,但现在我们实际上必须完成这项工作并渲染到一组固定的像素上。…

【Bluedroid】A2dp初始化流程源码分析

一、概述 Bluedroid是Android系统中用于蓝牙通信的底层协议栈,它支持多种蓝牙协议,包括A2DP(Advanced Audio Distribution Profile,高级音频分发协议)。A2DP主要用于通过蓝牙传输高质量音频,如立体声音乐。以下是Bluedroid中A2DP初始化的基本流程。 1.1. 启动Bluetooth…