LRU算法(最近最少使用算法)

151 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情

我之前写的MySQL第一步介绍InnoDB引擎的时候,提到Buffer Pool里缓存页信息时使用到LRU算法,它的LRU是基于传统LRU又额外优化出new区和old区,今天想使用java实现下传统的LRU算法。

基于HashMap和双向链表实现LRU

image.png

由双向链表实现LRU中数据的存储和对使用时间新旧的维护。

1.在链表头的是最新使用的。 2.在尾部的是最旧的。也是下次要清除的。 3.如果加入的值是链表内存在的则要移动到头部。

HashMap用来快速定位数据在双向链表中位置的,减少查询时候的时间复杂度。它是可以快速的(O(1)的时间)定位,链表中某个值是否存在。(每次遍历双向链表,时间复杂度为O(n)),定位到某个值存在后能马上获得他的node节点,因为是双向链表,直接用此节点的父节点,指向此节点的子节点。在将此节点放到头部就可以了。免除了遍历查找。

bean

    class DLinkedNode { 
        String key; 
        int value; 
        DLinkedNode pre; 
        DLinkedNode post; 
    }

LRU Cache

public class LRUCache {

private Hashtable<Integer, DLinkedNode> cache = new Hashtable<Integer, DLinkedNode>();
private int count;
private int capacity;
private DLinkedNode head, tail;

public LRUCache(int capacity) {
    this.count = 0;
    this.capacity = capacity;

    head = new DLinkedNode();
    head.pre = null;

    tail = new DLinkedNode();
    tail.post = null;

    head.post = tail;
    tail.pre = head;
}

public int get(String key) {
    DLinkedNode node = cache.get(key);
    if(node == null){
        return -1;
    }

    this.moveToHead(node);

    return node.value;
}


private void addNode(DLinkedNode node){
    node.pre = head;
    node.post = head.post;

    head.post.pre = node;
    head.post = node;
}

private void removeNode(DLinkedNode node){
    DLinkedNode pre = node.pre;
    DLinkedNode post = node.post;

    pre.post = post;
    post.pre = pre;
}


private void moveToHead(DLinkedNode node){
    this.removeNode(node);
    this.addNode(node);
}

private DLinkedNode popTail(){
    DLinkedNode res = tail.pre;
    this.removeNode(res);
    return res;
}

public void set(String key, int value) {
    DLinkedNode node = cache.get(key);

    if(node == null){

        DLinkedNode newNode = new DLinkedNode();
        newNode.key = key;
        newNode.value = value;

        this.cache.put(key, newNode);
        this.addNode(newNode);

        ++count;

        if(count > capacity){
            DLinkedNode tail = this.popTail();
            this.cache.remove(tail.key);
            --count;
        }
    }else{
        node.value = value;
        this.moveToHead(node);
    }
}

}

为什么不能使用队列来存储数据?

队列只能做到先进先出,但是重复用到中间的数据时无法把中间的数据移动到顶端。

为什么不能使用单链表?

单链表能实现新来的放头部,最久不用的在尾部删除。但删除的时候需要遍历到尾部,因为单链表只有头指针。在用到已经用到过的数据时,还要遍历整合链表,来确定是否用过,然后再遍历到响应位置来剔除的节点,并重新放在头部。这效率可想而知。

这时hashmap的作用就出来了,他可以在单位1的时间判断value的值是否存在,key 直接存储节点对象,能直接定位删除对应的节点 (将比节点的父节点指向此节点的子节点)。

要通过一个节点直接获得父节点的话,通过单链表是不行的。 这时双向链表的作用也提现出来了。能直接定位到父节点。这效率就很高了。而且由于双向链表有尾指针,所以剔除最后的尾节点也十分方便,快捷。

最后,鸣谢“空山鸟语as” 大佬的文章