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

新数据插入到链表头部;
每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
当链表满的时候,将链表尾部的数据丢弃。
实现
在进行实现前,以栈为例,整理下要实现的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决定着是否要做这个操作,那么调用方有哪些呢?

从上图,看出最常用LinkedHashMap.get()等方法都会触发此方法。
小结
当accessOrder为true时,则会把访问过的元素放在链表后面,放置顺序是访问的顺序。 当accessOrder为false时,则按插入顺序来遍历。
看到这里,另外一个特性:最新被访问的元素要移动到栈顶。
就是这样实现的。
拓展
是否还有可优化空间?
1、过期策略
传统的过期策略,是以固定的长度,通过对元素的排序,来剔除最近最久未使用的元素。
接触Redis之后,也可以利用Redis的过期时间来实现元素的剔除。
Last updated