如何实现 LRU 缓存淘汰算法

950 阅读2分钟

如何实现 LRU 缓存淘汰算法

这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

笔者最近在学习《数据结构与算法之美》,正好借着这个机会边练习边记录一下自己学习的知识点。不啰嗦,直接开始。

一、什么是缓存

缓存 一种提高数据读取性能的技术,有着广泛的应用,例如常见的 CPU 缓存、数据库缓存、浏览器缓存等等。

二、常见的缓存淘汰策略

缓存的大小是有限的。当缓存满了,哪些缓存数据应该被清除,哪些缓存数据应该被保留?这就应该由缓存淘汰策略来决定缓存数据的去留。常见的缓存淘汰策略有三种:

  • 先进先出策略 FIFO:先进入缓存中的数据,先被淘汰。
  • 最少使用策略 LFU:最近使用频率最少的缓存数据,先被淘汰。
  • 最近最少使用策略 LRU:最近一段时间最少被访问的数据,先被淘汰。

这里帮大家举个例子去记忆这三种淘汰策略,例如去年双十一买了一堆没用却用没有退货的东西,今年双十一又剁手买了一堆没有用的东西,房间堆满了,哪些该被清理掉?是不是我们清理的东西想法和这三种淘汰策略不谋而合了。接下来看我们今天的主角 LRU 最近最少使用策略具体实现思路吧。

三、LRU 淘汰策略实现思路

  • 缓存没有满时,新数据就放到缓存的前面。
  • 缓存已经满了,这是需要分两种情况去讨论:
    • 一是新的数据之前已经存在缓存中,此时我们需要将之前存在缓存中的数据清出缓存,再将新数据放到缓存的前面。
    • 二是新数据不在缓存中,此时我们需要先清除缓存中最后一个数据,再将新数据放到缓存的前面。

从这个思路我们可以看到,LRU 缓存淘汰策略的一个特点,需要频繁的进行数据插入删除操作。那么那种数据结构擅长进行删除和插入操作的呢?答案呼之欲出:链表。ok,接下来我们就使用链表结合实现思路转换成具体的代码。

四、LRU 淘汰策略代码实现

import java.util.LinkedList;

/**
 * @author xuls
 * @date 2021/11/14 16:23
 * LRU 缓存淘汰算法
 */
public class LRU <T>{
	private LinkedList<T> list;
	private int capacity;   //缓存容量

	public LRU() {
		this(10);
	}

	public LRU(int capacity) {
		this.capacity = capacity;
		list = new LinkedList<>();
	}

	public void add(T data){
		if (list.size() < capacity){
			//缓存未满,直接添加到链表头部,表明该数据最近在使用
			list.addFirst(data);
		}else {
			//链表之中是否包含 data
			if (list.contains(data)){
				//链表包含 data ,先将包含的数据移除
				list.remove(data);
			}else {
				//链表不包含 data ,移除最后一个,因为最后一个是最近最少使用的
				list.removeLast();
			}
			//将新数据添加到前面
			list.addFirst(data);
		}
	}

	@Override
	public String toString() {
		return "LRU{" +
				"list=" + list +
				'}';
	}

	public static void main(String[] args) {
		LRU<String> stringLRU = new LRU<>();
		for (int i = 0; i < 10; i++) {
			stringLRU.add("s"+i);
		}
		System.out.println(stringLRU);
		stringLRU.add("s1");
		System.out.println(stringLRU);
		stringLRU.add("s10");
		System.out.println(stringLRU);
	}
}