LRU

[TOC]

LRU

LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。

原理

如图

img
  1. 新数据插入到链表头部;

  2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;

  3. 当链表满的时候,将链表尾部的数据丢弃。

实现

在进行实现前,以栈为例,整理下要实现的2个特性:

  • 最新被访问的元素要移动到栈顶

  • 当新插入一个元素后,栈的长度不够用了,需要移除栈底的元素(即最近最少使用的元素)

实现一

实现一利用LinkedHashMap的特性,重写removeEldestEntry就可以完成LRU算法,先看代码:

下面来看为什么,首先看实现代码我们重写了removeEldestEntry方法,那么就来看官方的JavaDoc,此方法的作用:

JavaDoc大致翻译如下:

简单一点来说:

removeEldestEntry返回true表示需要移除最老的元素,返回false,则表示不需要移除最老的元素。

通过方法调用我们发现removeEldestEntry被afterNodeInsertion调用,afterNodeInsertion又被新加元素的方法调用。

到这里为止,已经知道了LRU中的移除最近未使用的元素是如何被实现的:在每次增加新的元素添加进map中,添加元素完成后,将会根据removeEldestEntry方法的返回值判断是否需要移除最老的元素(当然不仅仅只判断removeEldestEntry的返回值,removeEldestEntry的返回值仅仅是一个必要的条件)。

其次我们再看具体实现的部分:

除了重写removeEldestEntry方法外,还指定了initialCapacity、loadFactor、accessOrder,通过查阅JavaDoc,得到以下释义:

  • initialCapacity 初始容量

  • loadFactor 加载因子

  • accessOrder 排序方式,对于访问顺序,为 true;对于插入顺序,则为 false

前两个都可以理解,后面排序方式到底是什么意思呢?下面看2个小例子:

运行结果:

从运行结果看,当accessOrder为false的时候,元素的位置仅仅根据插入顺序,不会随着访问而改变;当accessOrder为true的时候,元素的位置在被访问过后,将会被移动到队尾。

那么看懂了表象,下面再来剖析LinkedHashMap如何完成这个实现的,同样透过源码,我们来看实现:

上述代码完成了如何将当前元素移动到队尾的任务,很显然accessOrder决定着是否要做这个操作,那么调用方有哪些呢?

afterNodeAccess

从上图,看出最常用LinkedHashMap.get()等方法都会触发此方法。

小结

当accessOrder为true时,则会把访问过的元素放在链表后面,放置顺序是访问的顺序。 当accessOrder为false时,则按插入顺序来遍历。

看到这里,另外一个特性:最新被访问的元素要移动到栈顶。

就是这样实现的。

拓展

  • 是否还有可优化空间?

    1、过期策略

    ​ 传统的过期策略,是以固定的长度,通过对元素的排序,来剔除最近最久未使用的元素。

    ​ 接触Redis之后,也可以利用Redis的过期时间来实现元素的剔除。

Last updated