一、LRU算法介绍

LRU算法全称Least Recently Used,也就是检查最近最少使用的数据的算法。这个算法通常使用在内存淘汰策略中,用于将不常用的数据转移出内存,将空间腾给最近更常用的“热点数据”。

算法很简单,只需要将所有数据按使用时间排序,在需要筛选出LRU数据时,取排名靠后的即可。

二、Java中的LRU实现思路

根据LRU算法,在Java中实现需要这些条件:

  • 底层数据使用双向链表,方便在链表的任意位置进行删除,在链表尾进行添加,这一点用单链表比较费劲,当然用数组等结构也都很费劲,当然双向链表在查找时也麻烦,但下述可以结合HashMap使用。
  • 需要将链表按照访问(使用)顺序排序。
  • 数据量超过一定阈值后,需要删除Least Recently Used数据。

三、Java中一个简单的LRUCache实现

对于上述的实现思路,java.util.LinkedHashMap已经实现了其中的99%,因此直接基于LinkedHashMap实现LRUCache非常简单。

LinkedHashMap为LRUCache铺垫了什么

  • 构造方法提供了accessOrder选项,开启后会get方法会有额外操作保证链表顺序按访问顺序逆序排列;

  • 底层结构使用双向链表,查找可以使用HashMap的特点;

  • 覆盖了父类HashMap的newNode方法和newTreeNode方法,这两个方法在HashMap中只是创建Node用的,而在LinkedHashMap中不但创建Node,还将Node放在链表末尾。

  • 父类HashMap提供了3个void的Hook方法,方法没做任何事:

  • afterNodeRemoval 父类在remove一个集合中存在的元素后调用;
  • afterNodeInsertion 父类在put、compute、merge后调用;
  • afterNodeAccess 父类在replace、compute、merge等替换值后会调用,LinkedHashMap在get中开启accessOrder时调用,究其根本是在对数据有操作时会调用;
  • LinkedHashMap本质上还是复用HashMap的绝大部分功能,包括底层的Node<K, V>[],因此能支持原本HashMap的功能。

  • 但是LinkedHashMap实现了父类HashMap的3个Hook方法:

  • afterNodeRemoval 实现链表的删除操作;
  • afterNodeInsertion 并没有实现链表的插入操作,但新添加了一个Hook方法boolean removeEldestEntry,当这个Hook方法返回true时,删除链表头的节点;
  • afterNodeAccess 如前所述,开启accessOrder后会将被操作的节点放在链表末尾,保证链表顺序按访问顺序逆序排列;

上一条3个方法是用来构建双向链表的,LinkedHashMap还覆盖了父类的3个方法:

  • newNode 在创建一个Node的同时,将Node添加到链表末尾;
  • newTreeNode 创建TreeNode的同时,将Node添加到链表末尾;
  • get 完成get功能的同时,如果accessOrder开启,会调afterNodeAccess将Node移动到链表末尾,覆盖newNode和newTreeNode方法后,在put方法中调用的newNode和newTreeNode方法也就连带实现了链表的插入操作

综上,我们可以了解到LinkedHashMap为什么能够轻松实现LRUCache。

  • 继承父类HashMap,拥有HashMap的功能,因此在查找一个节点时时间复杂度为O(1),再加上链表是双向,做链表任意节点的删除工作就非常简单。
  • 通过HashMap提供的3个Hook方法并覆盖了2个创建Node的方法,实现了自身链表的添加、删除工作,保证在不影响原本Array功能的前提下,正确完成自身的链表构建;这个过程实际上均是通过Hook方式增强原有功能的,因为原本的HashMap中创建节点其实也是使用的Hook方法。
  • 提供属性accessOrder并实现了afterNodeAccess方法,因此能够根据访问或操作顺序将最近使用或最近插入的数据放在链表尾,越久没被使用的数据就越靠近链表头,实现了整个链表按照LRU的要求排序。
  • 提供了一个Hook方法boolean removeEldestEntry,这个方法返回true时将会删除表头节点,即LRU中应当淘汰的节点,但是这个方法在LinkedHashMap中的实现永远返回false。

到这为止,实现一个LRUCache就很简单了:实现这个removeEldestEntryHook方法,给LinkedHashMap设置一个阈值,那么超过这个阈值时就会进行LRU淘汰。

示例:

// 继承LinkedHashMappublic class LRUCache<K, V> extends LinkedHashMap<K, V> {private final int MAX_CACHE_SIZE;public LRUCache(int cacheSize) {// 使用构造方法 public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)// initialCapacity、loadFactor都不重要// accessOrder要设置为true,按访问排序super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);MAX_CACHE_SIZE = cacheSize;}@Overrideprotected boolean removeEldestEntry(Map.Entry eldest) {// 超过阈值时返回true,进行LRU淘汰return size() > MAX_CACHE_SIZE;}}

注:LinkedHashMap是线程不安全的,可以参考Dubbo中com.alibaba.dubbo.common.utils.LRUCache的实现。

四、LRU缓存命中率问题

当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重。

分析:
LRU只适合缓存热点数据,偶发性、周期性的批量操作会将LRU的热点缓存污染,于是像LRU-K、URL-Two queues原理、Multi Queue原理等都是将热点数据识别出来再放入LRU缓存队列,但是热点数据识别都是有一个识别窗口的,比如最近10000次内操作过两次的认为是热点数据会放入LRU缓存,避免LRU缓存的热点数据被污染掉,降低缓存命中率。

具体的解决方案大家可以参考:https://www.jianshu.com/p/d533d8a66795

LRU算法的Java实现相关推荐

  1. LRU算法及Java实现

    LRU算法介绍 LRU是Least Recently Used的缩写,即最近最少使用,常用于页面置换算法,为虚拟页式存储管理服务.LRU算法的提出,是基于这样一个事实:在前面几条指令中使用频繁的页面很 ...

  2. 未使用的分配java,最近最久未使用页面淘汰算法———LRU算法(java实现)

    LRU算法,即Last Recently Used ---选择最后一次访问时间距离当前时间最长的一页并淘汰之--即淘汰最长时间没有使用的页 按照最多5块的内存分配情况,实现LRU算法代码如下: pub ...

  3. LRU算法(JAVA实现)

    一.算法介绍 最近最久未使用(Least Recently Used    LRU)算法是⼀种缓存淘汰策略,它是大部分操作系统为最大化页面命中率而广泛采用的一种页面置换算法.该算法的思路是,发生缺页中 ...

  4. 缓存淘汰算法--LRU算法

    缓存淘汰算法--LRU算法 参考: https://www.cnblogs.com/dailidong/p/7571178.html https://blog.csdn.net/wangxilong1 ...

  5. java 最少使用(lru)置换算法_「面试」LRU了解么?看看LinkedHashMap如何实现LRU算法...

    以下内容均是本人原创,希望你看完之后能有更多更深入的了解,欢迎关注➕ 问题:使用Java完成一个简单的LRU算法 什么是LRU算法 LRU(Least Recently Used),也就是最近最少使用 ...

  6. 使用java.util.LinkedList模拟实现内存页面置换算法--LRU算法

    一,LRU算法介绍 LRU是内存分配中"离散分配方式"之分页存储管理方式中用到的一个算法.每个进程都有自己的页表,进程只将自己的一部分页面加载到内存的物理块中,当进程在运行过程中, ...

  7. java 最少使用(lru)置换算法_缓存置换算法 - LRU算法

    LRU算法 1 原理 对于在内存中并且不被使用的数据块就是LRU,这类数据需要从内存中删除,以腾出空间来存储常用的数据. LRU算法(Least Recently Used,最近最少使用),是内存管理 ...

  8. 缓存淘汰策略—LRU算法(java代码实现)

    LRU 原理 LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是"如果数据最近被访问过,那么将来被访问的几率也更高" ...

  9. LRU算法java实现

    LRU全称是Least Recently Used,即最近最久未使用的意思. LRU算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小.也就是说,当限定的空间已 ...

最新文章

  1. JavaScript正则表达式之语法
  2. 爬虫网页搜索策略的选择
  3. 【分组并查集讲解】A Bug‘s Life(例题)
  4. ACM第一天研究懂的AC代码——BFS问题解答——习题zoj2165
  5. LiveVideoStackCon讲师热身分享 ( 十三 ) —— Intel QSV技术在FFmpeg中的实现与使用
  6. Android开发之自带阴影效果的shape
  7. Java 8:开发人员怎么看?
  8. 【ZOJ - 3872】Beauty of Array(思维,计算贡献,枚举)
  9. 如何通过父类引用“调用”子类所独有的方法
  10. 实现接口与显式实现接口的区别
  11. Fortran入门教程(七)——数组
  12. Python爬虫——新浪微博登陆
  13. [生存志] 第11节 历代大事件概览 春秋
  14. 宋体小四在手机上是几号_word文档4号宋体 word宋体小四是几号字
  15. Excel宏的录制和解密
  16. CREATE TABLESPACE命令详解
  17. 【Markdown使用技巧总结】-如何在Markdown文档中插入空格?
  18. 梦想世界3手游服务器维护,梦想世界手游进不去 闪退及登录不上解决方法
  19. Flask 和 Django 的比较和选择
  20. github上传大文件

热门文章

  1. Socketio下载以及使用方法
  2. 开学季蓝牙耳机推荐哪个好?性价比高音质好的学生党蓝牙耳机
  3. 动态生成洛谷个人练习/估值数据
  4. 网络性能评估工具Iperf详解(可测丢包率)
  5. SQL中IN和OR效率对比
  6. 数学建模:评价性模型学习——灰色关联分析法(GRA模型)
  7. c语言智力题 操作符详解例题 数据存储 指针初阶 水仙花数 杨辉三角 逆序字符串 喝汽水问题 打印图形 猜凶手 使用指针打印数组内容 调整奇数偶数顺序 运动员猜名次
  8. vs中代码旁边有个小锁,解锁教程
  9. php正则表达式 定界符,PHP正则表达式之定界符和原子介绍
  10. Gradle build.gradle 文件