(1)是什么?
Least Recently Used (最近最少使用)
缓存淘汰算法中的一种。因为缓存是保存在内存中的,内存是很吃紧很宝贵的,所以要保证最热点的数据做缓存,冷门数据淘汰。
(2)具体在哪些场景使用?
Mysql的缓冲池,Linux OS 的虚拟文件系统VFS都有用到。
(3)具体实现步骤?
既然是缓存,那肯定就是List中存放着一大串 Key - Value 结构,我们查找目标数据的时候就是for这个List找到对应的Key,这个List可能很长,所以我们可以把热点数据排到较前面,冷门的放后面,如果超过了长度,就淘汰,移除缓存List。
传统的LRU如下:
(1)首先定义一条链表List,初始化之初,它连续加载1~6的磁盘块到缓存中
1->2->3->4->5->6 六个page cache缓存页
(2)查询3,则3的page cache命中,遍历List并且将3移至头部(加快下次检索速度)
3->1->2->4->5->6
(3)查询7,发现缓存链表中没有page cache 命中,则读磁盘,并将7加入到缓存List头部,剔除掉列尾元素6
7->3->1->2->4->5
(4)牛客也是有道LRU的算法题,描述的也是这个思路:
(5)思路很清晰,也比较好做,代码实现:
首先是比较简单的调用方法:
public class SplitApplication {
private static List resultArray = new ArrayList();
public static void main(String[] args) {
int[][] twoArray = new int[][]{{1,1,1},{1,2,2},{1,3,3},{2,1},{1,4,4},{2,2}};
int listSize = 3;
testLRU(twoArray, listSize);
}
public static void testLRU(int[][] twoArray, int listSize){
LRU lru = new LRU(3);
for (int i = 0; i < twoArray.length; i++) {
Map<Integer,Integer> map = new HashMap<>(1);
int[] oneArray = twoArray[i];
System.out.println(Arrays.toString(oneArray));
// 操作标志位独立拿出来
int opt = oneArray[0];
// 获取剩下的参数
if (opt==1){
// 表示set(x, y)
lru.set(oneArray[1],oneArray[2]);
} else if (opt == 2){
// 表示get(x),若x未出现过或已被移除,则返回-1
int result = lru.get(oneArray[1]);
resultArray.add(result);
}
}
System.out.println("查询结果"+resultArray.toString());
}
}
然后重头戏当时是LRU 实体类的定义:
public class LRU {
private List<Map<Integer,Integer>> mapList;
int maxSize;
public LRU(int maxSize) {
this.maxSize = maxSize;
this.mapList = new ArrayList<>(maxSize);
}
public void set(Integer key ,Integer value){
Map<Integer, Integer> map = new HashMap<>(1);
map.put(key,value);
if (mapList.size() >= maxSize) {
System.out.println("超过了大小,将会扣除末尾元素再重新设置");
this.mapList.remove(mapList.size() - 1);
}
this.mapList.add(map);
System.out.println("设置新值之后:"+this.mapList.toString());
resortList(mapList, key, value);
}
public Integer get(Integer key){
Integer result = null;
for (int i = 0; i < mapList.size(); i++) {
Map<Integer, Integer> map = this.mapList.get(i);
result = map.get(key);
if (result!=null){
// 找到了才重新触发热点排序
resortList(this.mapList, key ,result);
return result;
}
}
return -1;
}
public void resortList(List<Map<Integer,Integer>> list, Integer key, Integer value){
// 把操作/读取的目标数据移动到最前面
Map<Integer, Integer> map = null;
Iterator<Map<Integer,Integer>> iterator = list.iterator();
while (iterator.hasNext()){
map = iterator.next();
if (map.get(key) == value ){
iterator.remove();
}
}
// 每次操作的目标数据都设置为热点第一位
list.add(0, map);
System.out.println("重新排序后:"+this.mapList.toString());
}
public List<Map<Integer, Integer>> getMapList() {
return mapList;
}
public void setMapList(List<Map<Integer, Integer>> mapList) {
this.mapList = mapList;
}
public int getMaxSize() {
return maxSize;
}
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
}
(1)在这里我们定义了List<Map<Integer,Integer>> mapList 结构来存放KV形式的缓存信息。通过有参构造来指定List的大小。
(2)定义Set方法往缓存List放元素,同时判断是否超出了大小,如果是,移除最后一个元素。如果不是,设置新值到头部。
(3)定义get方法获取缓存List的缓存信息,遍历List并且根据Key获取Value。
(4)不管get还是set,进行操作后都要调用 resortList 方法对List中的元素进行重新的排序。
(5)resortList 的主要任务,就是把刚才操作的数据当作热点数据移到最前面。
(6)就这么简单,考察的更多的是基础的逻辑判断和容器的使用。
执行main方法看效果: