剖析LRU算法及LinkedHashMap源码实现机制

2024-09-06 10:08

本文主要是介绍剖析LRU算法及LinkedHashMap源码实现机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、简述

LRU(Least Recently Used),注意L代表的不是Latest,翻译成中文通常叫:近期最少使用算法、最近最少使用算法。LRU与LFU(Least Frequently Used)、FIFO(First In First Out)是3种常用的缓存算法(页面置换算法)。缓存算法的应用场景有很多,例如操作系统在物理内存不足时触发的磁盘交换、CPU中L1、L2、L3缓存淘汰替换、超市中畅销货品在货架的摆放位置优化等。缓存算法的实现和变种也有很多,这里只针对LRU算法抛砖引玉。

二、算法原理

缓存算法的根本目的,是淘汰掉“最不常用”的元素。LRU算法淘汰的是截止当前缓存区中最久没有被访问过的元素,这意味着“最后一次访问时间”是LRU算法决定是否淘汰元素的唯一标准。

因此,假设我们记录下每个元素最后一次被访问的时间戳,并按照此时间戳早晚进行排序,那么,时间最早的元素应该被淘汰掉(如图1)。所以,将LRU准确地翻译成中文,应该是:距今最久未用淘汰算法。

图1:最后一次访问时间最早的元素首先被淘汰

这里补充一点,LRU算法并不是绝对公平的。举个例子,假如某个元素仅仅在淘汰执行前被访问了1次,在之前的数百万次的请求中从未被访问,按照LRU的算法逻辑,它仍然会留存下来,这其实无法准确描述“是否常用”这一特性。“是否常用”其实在不同的业务场景有不同的定义,大家可以细细体会。

三、LRU的实现机制

依照LRU算法原理,其实现机制并不复杂,仅仅需要根据元素的最后一次访问时间排序即可。但当运用在1个生产环境的缓存系统中时,会面临以下几个工程问题:

  1. 在缓存访问频次极高的情况下,时间戳即使精确到纳秒,依然存在大量的相同时间戳,排序无效。若采用递增ID替代时间戳,则存在溢出的风险。
  2. 每次进行缓存淘汰时,都需要将缓存区所有元素进行排序。当元素数极多时,缓存系统的性能将急剧下降,CPU耗费极高。
  3. 在元素本身占用内存不大的情况下,附加的“时间戳/自增ID”甚至在内存占用上喧宾夺主。

最佳的实现思路是利用链表的有序特性,将缓存元素的“按最后一次访问时间戳排序”巧妙地转换为其在链表中的相对顺序。因为LRU算法关心的并不是元素的绝对访问时间,而是元素被访问的先后顺序。即:元素被命中时,移到链表头。执行淘汰时,从链表尾开始淘汰。因此,LRU算法实现的核心数据结构是:循环链表。

但对于一个<K,V>缓存系统而言,仅有链表结构是不够的。链表虽然解决了缓存淘汰问题,但缓存访问却需要每次都从链表头开始遍历。JDK中的java.util.LinkedHashMap给出了最佳的实现(链表也是Linked这一前缀的由来),同时解决了元素的快速访问与缓存淘汰问题。

四、JDK中LinkedHashMap对LRU的实现源码分析

1、概述

从Oracle的JDK源码的注释中可以看到,从JDK1.4开始,Josh Bloch就提供了LinkedHashMap供开发者使用。LinkedHashMap继承自HashMap,拥有<K,V>结构的同时还提供了缓存淘汰的扩展。

 

2、LinkedHashMap的使用示例(LRU效果)

 

上面的代码打印结果为:abce,很正常,按照加入的顺序打印

打印结果为:ceab ,可以看出LinkedHashMap构造参数中的“true”启用了其内部的LRU 特性。

3、利用LinkedHashMap的LRU特性,构造固定容量缓存系统

 

说明:上述代码中FixedSizeCahceMap构造参数中的fixSize指定了缓存的最大元素个数。removeEldestEntry方法重写了LinkedHashMap中关于判断是否删除“最久未用元素”的逻辑,“return size() > fixSize”表明,当缓存中的元素数达到fixSize时,将eldest元素淘汰掉。

4、LinkedHashMap实现源码分析

上文提到LinkedHashMap继承自HashMap,其主要目的是充分利用HashMap现有的<K,V>数据结构,在此基础上通过Override一些关键方法,将<K,V>结构与循环链表结构完美结合运用。被Override的几个关键点:

(1)定义链表的节点

(2)LinkedHashMap初始化时,建立循环链表

 

(3)向缓存中添加元素时的重写

(4)缓存命中时的方法重写

五、总结

LRU算法是缓存算法的入门必修课,而使用缓存算法也是很多互联网基础设施研发的必经之路,尤其在数据规模不断增大而物理资源相对有限的分布式领域,深入了解其机制原理并触类旁通,将是夯实高阶研发基础的第一步。

这篇关于剖析LRU算法及LinkedHashMap源码实现机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1141725

相关文章

Python实现精准提取 PDF中的文本,表格与图片

《Python实现精准提取PDF中的文本,表格与图片》在实际的系统开发中,处理PDF文件不仅限于读取整页文本,还有提取文档中的表格数据,图片或特定区域的内容,下面我们来看看如何使用Python实... 目录安装 python 库提取 PDF 文本内容:获取整页文本与指定区域内容获取页面上的所有文本内容获取

基于Python实现一个Windows Tree命令工具

《基于Python实现一个WindowsTree命令工具》今天想要在Windows平台的CMD命令终端窗口中使用像Linux下的tree命令,打印一下目录结构层级树,然而还真有tree命令,但是发现... 目录引言实现代码使用说明可用选项示例用法功能特点添加到环境变量方法一:创建批处理文件并添加到PATH1

Java使用HttpClient实现图片下载与本地保存功能

《Java使用HttpClient实现图片下载与本地保存功能》在当今数字化时代,网络资源的获取与处理已成为软件开发中的常见需求,其中,图片作为网络上最常见的资源之一,其下载与保存功能在许多应用场景中都... 目录引言一、Apache HttpClient简介二、技术栈与环境准备三、实现图片下载与保存功能1.

canal实现mysql数据同步的详细过程

《canal实现mysql数据同步的详细过程》:本文主要介绍canal实现mysql数据同步的详细过程,本文通过实例图文相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的... 目录1、canal下载2、mysql同步用户创建和授权3、canal admin安装和启动4、canal

Maven 配置中的 <mirror>绕过 HTTP 阻断机制的方法

《Maven配置中的<mirror>绕过HTTP阻断机制的方法》:本文主要介绍Maven配置中的<mirror>绕过HTTP阻断机制的方法,本文给大家分享问题原因及解决方案,感兴趣的朋友一... 目录一、问题场景:升级 Maven 后构建失败二、解决方案:通过 <mirror> 配置覆盖默认行为1. 配置示

Nexus安装和启动的实现教程

《Nexus安装和启动的实现教程》:本文主要介绍Nexus安装和启动的实现教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、Nexus下载二、Nexus安装和启动三、关闭Nexus总结一、Nexus下载官方下载链接:DownloadWindows系统根

SpringBoot集成LiteFlow实现轻量级工作流引擎的详细过程

《SpringBoot集成LiteFlow实现轻量级工作流引擎的详细过程》LiteFlow是一款专注于逻辑驱动流程编排的轻量级框架,它以组件化方式快速构建和执行业务流程,有效解耦复杂业务逻辑,下面给大... 目录一、基础概念1.1 组件(Component)1.2 规则(Rule)1.3 上下文(Conte

MySQL 横向衍生表(Lateral Derived Tables)的实现

《MySQL横向衍生表(LateralDerivedTables)的实现》横向衍生表适用于在需要通过子查询获取中间结果集的场景,相对于普通衍生表,横向衍生表可以引用在其之前出现过的表名,本文就来... 目录一、横向衍生表用法示例1.1 用法示例1.2 使用建议前面我们介绍过mysql中的衍生表(From子句

Mybatis的分页实现方式

《Mybatis的分页实现方式》MyBatis的分页实现方式主要有以下几种,每种方式适用于不同的场景,且在性能、灵活性和代码侵入性上有所差异,对Mybatis的分页实现方式感兴趣的朋友一起看看吧... 目录​1. 原生 SQL 分页(物理分页)​​2. RowBounds 分页(逻辑分页)​​3. Page

Python基于微信OCR引擎实现高效图片文字识别

《Python基于微信OCR引擎实现高效图片文字识别》这篇文章主要为大家详细介绍了一款基于微信OCR引擎的图片文字识别桌面应用开发全过程,可以实现从图片拖拽识别到文字提取,感兴趣的小伙伴可以跟随小编一... 目录一、项目概述1.1 开发背景1.2 技术选型1.3 核心优势二、功能详解2.1 核心功能模块2.