LRU Cache的设计思想

2,539 阅读2分钟

前言

LRU在实际生活中的使用场景非常多。

  • 比如手机切换到任务界面,你最近使用过的几个APP总会在按照时间排在最前面。
  • 比如你总是会把你最近在看的书,放在你的桌子上,书架上摆放的都是不常看的。
  • 再比如,我们会把所有有用的信息记录在电脑或网盘里,但是最常用的信息,我们会牢牢地记在脑子里。

一、LRU Cache的定义

LRU Cache算法,又称为近期最少使用算法。我们会设置一个容量固定的存储空间,比如电脑/手机的内存,但是如果这个内存要被数据占满了,此时又要存入新的数据,我们就要把近期最少使用的资源从缓存里剔除掉,并把新的数据加入到缓存中。

二、设计思想

LRU设计思想.png

  1. 方框内的元素都是内存未被填满的情况,直接存入即可,但是要保证最新存入的要在队列的最上面,最久未使用的要在最下面。
  2. 第①步:当存入C元素时,最久未使用的元素A被剔除。而元素C本来就存在于队列中,所以要把C的位置替换到队列的最上面。
  3. 第②步,当存入G元素时,剔除最久未使用的B元素,并且把G插在队列最上方。 这就是LRU的基本思想

三、具体实现

LRU Cache实现的要素主要有两个:缓存容量替换策略 要求查询、修改和更新任意元素的时间复杂度为O(1)。
这里我们用hash表双向链表来实现LRU

具体实现.png

双向链表用来模拟最久先出的队列。HashMap用来存储节点标识和节点对象的关系,这样就能实现查询、修改和更新任意元素的时间复杂度为O(1)

public class LRUCache {
    static class Node {
        public int key, val;
        public Node(int k, int v) {
            this.key = k;
            this.val = v;
        }
    }
    // LRU容量
    int capacity;
    // HashMap
    HashMap<Integer, Node> map;
    // 双向链表
    LinkedList<Node> cache;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        map = new HashMap<>(capacity);
        cache = new LinkedList<>();
    }
    
    public int get(int key) {
        if (!map.containsKey(key)) return -1;
        Node node = map.get(key);
        // 获取到了元素之后,要把获取的元素排到队列的最上面
        cache.remove(node);
        cache.addFirst(node);
        return node.val;
    }
    
    public void put(int key, int value) {
        Node node = new Node(key, value);
        if (map.containsKey(key)) {
            // 把已经存在的值,从链表中remove掉
            cache.remove(map.get(key));
        } else if (capacity == map.size()) {
            // 如果链表已经达到了容量,把链表最后的元素remove掉
            Node last = cache.removeLast();
            map.remove(last.key);
        }
        // 将最新放入的元素排在队列最前面
        cache.addFirst(node);
        map.put(key, node);        
    }
}