LRU算法的实现

708 阅读2分钟

       LRU(Least recently used,最近最少使用)是缓存清理的策略,其思想是如果数据最近被访问过,未来被访问的几率就会很大。LRU算法对数据的增、删、查操作非常频繁,为了保证最少的时间复杂度,所以采用哈希+双链表的结构来实现。    

      首先定义一个双链表节点Node。节点里的元素包括key,值,前驱,后继。

class Node {
int key; 
int val; 
Node prev;
Node next; 
}

      接着定义算法中的变量,并初始化。变量包括缓存容量、头节点、尾节点、存储key的hash表。

private int capacity;
private Node first;
private Node last;
private Map<Integer, Node> map;
public LRUCache(int capacity) {
        this.capacity=capacity;
        map=new HashMap<>();
        first=new Node();
        last=new Node();
        first.next=last;
        last.prev=first;
    }

       定义两个方法。一个将节点插入到头结点的后继。另一个是删除掉尾节点的前驱。由于是双向链表,所以插入删除的时候要同时对他们的前驱节点和后继节点进行修改。

       当往头结点后插入的时候,需要判断该节点的key是否已经存在于链表中,若存在要先从原来的位置删除掉节点,然后再插入。

       当删除尾节点时,要同步删除掉map中的key,所以才要在节点中定义key。,可以迅速从map中删掉它。

//将节点插入到头结点后
public void addToHead(Node node){
        if(node.next!=null){
            node.next.prev=node.prev;
        }
        if(node.prev!=null){
            node.prev.next=node.next;
        }
        node.next=first.next;
        node.prev=first;
        first.next=node;
        node.next.prev=node;
    }
//将尾节点的前一个节点删除
    public void removeFromTail(){
        int key=last.prev.key;
        last.prev.prev.next=last;
        last.prev=last.prev.prev;
        map.remove(key);
    }

       当使用get方法获取数据时,先把节点插入到头结点后,然后返回节点的value值。

public int get(int key) {
        if(map.containsKey(key)){
            Node node=map.get(key);
            addToHead(node);
            return map.get(key).val;
        }else{
            return -1;
        }
    }

       当使用put方法放置数据时,先判断key是否存在,若存在则直接修改对应的value,并把节点移动到头节点。若不存在则要判断缓存容量是否已经上限,若上限则需要先删除掉尾节点,然后再插入。

public void put(int key, int value) {
        Node node=new Node();
        if(map.containsKey(key)){
            node=map.get(key);
        }else{
            if(map.size()==capacity){
                removeFromTail();
            }
        }
        map.put(key,node);
        node.key=key;
        node.val=value;
        addToHead(node);
    }

       Redis中的近似LRU算法

       上述算法虽然时间复杂度很低,但双链表结构会占用更多的存储空间。这对于数据量很大的场景下是很不合适的。所以Redis采用了一种近似LRU算法。即从全部数据中随机抽取一定数量的数据,并按访问时间进行排序,淘汰掉最不常用的。抽取的样本数量越多,Redis的近似LRU算法就会越接近真实的LRU算法,同时损耗也会变大,默认的样本数量是5。