【LRU】概念详解及实现

1.什么是LRU?

        LRU(Least Recently used):直译是最近最少使用,但我觉得与它本身所表示出来的性质不同,我更喜欢翻译为最近最久未使用。

举个例子:

【LRU】概念详解及实现_第1张图片

        在我们在使用手机的过程中,如果打开过多个应用程序,我们进入后台就可以看到上图所示的现象:我们最后一个使用的应用程序会排在最前面,而最早被使用且最近未被使用的应用程序则会排在末尾,如果手机有后台应用数限制,那么最早被使用的应用程序在打开的后台数超出限制时,会被移除,而这就是LRU算法的运作方式

2.LRU有什么用?

        LRU算法一般是作为Redis内存淘汰策略的一种实现:新的key要存入Redis内存,但此时Redis内存已到达上限,那么使用LRU算法就可以淘汰掉最久未被使用的key,将冷数据(用户最不常访问的数据)剔除,并将热数据(用户经常访问的数据)留存在Redis中

3.LRU实现

        LRU由两个重要的数据结构组成:双向链表(DuplexList)、哈希表(HashMap)。既然是双向链表,那么链表中的节点一定包含两个指针,分别指向前继节点和后继节点;哈希表的key对应redis的key,value对应双向链表中的节点Node;为了简化,key和val都认为是int类型

节点Node(存储key和value):

public class Node {
    public int key, val;
    public Node next, prev;
    public Node(int k, int v) {
        this.key = k;
        this.val = v;
    }
}

双向链表DuplexList(存储Node):

class DuplexList {  
    // 头、尾虚拟节点
    private Node head, tail;  
    // 链表节点个数
    private int size;
    
    public DuplexList() {
        //初始化双向链表
        head = new Node(0, 0);
        tail = new Node(0, 0);
        head.next = tail;
        tail.prev = head;
        size = 0;
    }

    // 在链表尾部添加节点 x,时间 O(1)
    public void addLast(Node x) {
        x.prev = tail.prev;
        x.next = tail;
        tail.prev.next = x;
        tail.prev = x;
        size++;
    }

    // 删除链表中的 x 节点(x 一定存在)
    // 由于是双链表且给的是目标 Node 节点,时间 O(1)
    public void remove(Node x) {
        x.prev.next = x.next;
        x.next.prev = x.prev;
        size--;
    }
    
    // 删除链表中第一个节点,并返回该节点,时间 O(1)
    public Node removeFirst() {
        if (head.next == tail)
            return null;
        Node first = head.next;
        remove(first);
        return first;
    }

    // 返回链表长度,时间 O(1)
    public int size() { return size; }

}

LRUCache:

class LRUCache {
    // 最大容量

    private int capacity;

    //快速访问缓存,value->Node

    private HashMap map

    //链表

    private DuplexList cache;

    public LRUCache(int capacity){

            this.capacity = capacityl

            map = new HashMap<>();

            cache = new DuplexList();

} 

    /* 将某个 key 变为最近使用的 */
    private void makeRecently(int key) {
        Node x = map.get(key);
        // 先从链表中删除这个节点
        cache.remove(x);
        // 重新插到队尾
        cache.addLast(x);
    }

    /* 添加最近使用的元素 */
    private void addRecently(int key, int val) {
        Node x = new Node(key, val);
        // 链表尾部就是最近使用的元素
        cache.addLast(x);
        // 别忘了在 map 中添加 key 的映射
        map.put(key, x);
    }

    /* 删除某一个 key */
    private void deleteKey(int key) {
        Node x = map.get(key);
        // 从链表中删除
        cache.remove(x);
        // 从 map 中删除
        map.remove(key);
    }

    /* 删除最久未使用的元素 */
    private void removeLeastRecently() {
        // 链表头部的第一个元素就是最久未使用的
        Node deletedNode = cache.removeFirst();
        // 别忘了从 map 中删除它的 key
        int deletedKey = deletedNode.key;
        map.remove(deletedKey);
    }

    /* 通过key获取value */

     public int get(int key){

          if(!map.containsKey(key)){
                return -1;
        }

        //将数据变为最近使用
        makeRecently(key);

        return map.get(key).val;

     }

     /* 添加key到缓存中 */

     public void put(int key,int val){

        //如果缓存中已经存在这个key

        if(map.containsKey(key){

                //先删除key,再加入key,让Node排在链表尾部(最前面)

                deleteKey(key);

                addRecently(key,val);

                return;

        }

               //如果缓存中不存在这个key,同时缓存容量到达上限

        if(cap == cache.size()){

                //删除最久未被使用过的key(即链表头部的节点)

                removeLeastRecently();

        }

                //最后将Node存储到链表中,同时更新Map

                addRecently(key,val);

     }
}

        以上是本文关于LRU的全部内容,如有错误,请指正,感谢浏览!!!

你可能感兴趣的:(java)