每日一题:LRU算法原理和实现

220 阅读3分钟

LRU是Least Recently Used的缩写,即最近最少使用,常用于页面置换算法,是为虚拟页式存储管理服务的。

LRU 算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。

比如:计算机为每个进程分配三块可用物理内存,现在数据为0,1,2,3,4。所以不可能把这5个数据都放在物理内存之中,每个数据的使用权重也是不一样的。比如数据使用顺序为1,3,4,1,2,0,4,2。假如一开始三块内存块都为空,我们来模拟一下LRU算法的过程:

这就是LRU算法的基本原理,那我们应该怎么实现LRU算法呢?

LRU算法实现


1.首先要保证数据可以随时取用,比如内存块中的数据为1,3,2.取1,3,2的复杂度都应该为O(1),并且需要取中间数进行刷新到头部,这很符合散列表的特性。所以我们会利用Java中的HashMap进行数据的存储获取

2.数据应该是有序的,我需要保证每次插入头节点的数据是最新的,去除尾部的历史数据。这种头尾更新数据的操作用数据需要频繁移动数据,所以需要用链表。当更新中间数据时需要链接前后链表,删除尾部时需要更新尾部指针到它上一个指针的位置,所以双向链表更方便这种操作。

3.用队列实现这种先后顺序行吗?不可以,因为需要查找到中间已存在的元素更新到头部。

1.准备一个双向链表数据结构

class DoubleLinkedNode {
    int key;
    int value;
    DoubleLinkedNode pre;
    DoubleLinkedNode post;
}

对于一个LRU算法实现,需要至少两个操作: get(key),set(key,value)

当get(key)的时候,如果key不存在,返回-1,若key存在,刷新对应的数据到链表头部

put(key,value) ,如果key存在,获取对应的值并刷新到头部。如果不存在,存储值到头部,若存储的数量已超出最大值,则去掉尾部的节点并移除掉map中的数据。

具体的实现如下:

import java.util.HashMap;

public class LRUCache {

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

    /**
     * 构造LRU,设置两个头尾节点
     * @param capacity
     */
    public LRUCache(int capacity) {
        this.count = 0;
        this.capacity = capacity;
        head = new DoubleLinkedNode();
        tail = new DoubleLinkedNode();
        head.post = tail;
        tail.pre = head;
    }

    /**
     * 获取key对应的值
     * @param key 
     * @return 值
     */
    public int get(int key) {

        DoubleLinkedNode node = cache.get(key);
        if(node == null){
            return -1;
        }

        this.moveToHead(node);

        return node.value;
    }


    /**
     * 设置key,value
     * @param key
     * @param value
     */
    public void put(int key, int value) {
        DoubleLinkedNode node = cache.get(key);

        if(node == null){

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

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

            ++count;

            //如果超过最大值则删除尾部节点和map中的数据
            if(count > capacity){
                DoubleLinkedNode tail = this.popTail();
                this.cache.remove(tail.key);
                --count;
            }
        }else{
            node.value = value;
            this.moveToHead(node);
        }
    }

    /**
     * 插入头节点
     * @param node
     */
    private void addNode(DoubleLinkedNode node){
        node.pre = head;
        node.post = head.post;

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

    /**
     * 删除节点
     * @param node
     */
    private void removeNode(DoubleLinkedNode node){
        DoubleLinkedNode pre = node.pre;
        DoubleLinkedNode post = node.post;

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

    /**
     * 移动到头节点
     * @param node
     */
    private void moveToHead(DoubleLinkedNode node){
        this.removeNode(node);
        this.addNode(node);
    }

    /**
     * 删除尾节点
     * @return 尾节点
     */
    private DoubleLinkedNode popTail(){
        DoubleLinkedNode res = tail.pre;
        this.removeNode(res);
        return res;
    }
}

class DoubleLinkedNode {
    int key;
    int value;
    DoubleLinkedNode pre;
    DoubleLinkedNode post;
}

LRU算法在很多框架中有很对应的实现或者变种实现,目前就介绍到这里.