Java实现LRU缓存

前言

在操作系统中,页面置换算法中有一种思想叫做LRU,就是选择最近最少使用的页将其置换出去。LRU是一种缓存淘汰策略,那么在Java中结合学习过的基础数据机构如何能实现LRU效果呢?

解决思路

首先要知道Java的内置容器LinkedHashMap已经可以实现LRU缓存,具体做法如下:

class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private static final int MAX_ENTRIES = 3;

    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > MAX_ENTRIES;
    }

    LRUCache() {
        super(MAX_ENTRIES, 0.75f, true);
    }
}

去查看源码可以发现jdk内部维护了一个双向链表,用来维护插入顺序或者 LRU 顺序,继承自 HashMap,因此具有和 HashMap 一样的快速查找特性。
由此启发,可以使用map + 双向链表的思想来实现LRU缓存。

具体实现

双向链表节点比单向链表接节点多了一个指向前驱节点的指针,使用双向链表的目的是为了能在O(1)时间里删除一个节点、删除末尾节点。
链表节点定义如下:

class MyNode{
    public int key, value;
    public MyNode next, pre;
    public MyNode(int key, int value){
        this.key = key;
        this.value = value;
    }
}

双向链表需要实现往头部插入一个节点、删除一个节点、删除尾部节点并返回这几个操作。为了在常数级时间里完成这三个操作,需要维护头指针和尾指针分别指向链表的头和尾。

class DoubleList{
    public MyNode head, tail;
    public int size;
    public void addFirst(MyNode node){
        if (head == null){ //链表为空时
            head = tail = node;
        }else {
            MyNode cur = head;
            cur.pre = node;
            node.next = cur;
            head = node;
        }
        size++; //每添加一个节点,链表长度加一
    }
    public void remove(MyNode node){
       if (head == node && tail == node){ //链表只有一个节点时或者链表为空时
           head = null;
           tail = null;
       }else if (tail == node){ //当要删除的节点是尾节点时
           node.pre.next = null;
           tail = node.pre; //更新尾节点
       }else if (head == node){ //要删除的节点是头节点时
           node.next.pre = null;
           head = node.next; //更新头节点
       }else { //删除节点位于链表的中间位置
           node.pre.next = node.next;
           node.next.pre = node.pre;
       }
       size--; //删除节点,长度减一
    }
    public MyNode removeLast(){
        MyNode node = tail; //记录当前尾节点
        remove(tail);//删除节点
        return node; //返回尾节点
    }
}

下面就要实现LRU缓存,map可以保证查找效率为常数级,链表能保证删除时间复杂度为常数级。

public class LRUCache146{
    private int capacity;
    private HashMap<Integer, MyNode> map = new HashMap<>();
    private DoubleList list;

    public LRUCache146(int capacity){
        this.capacity = capacity;
        this.list = new DoubleList();
    }
    public int get(int key) {
        if (!map.containsKey(key)) 
        	return -1;
        int val = map.get(key).value;
        put(key, val);
        return val;
    }
    public void put(int key, int value){
        MyNode cur = new MyNode(key, value);
        if (map.containsKey(key)){
            list.remove(map.get(key));
        }else {
            if (capacity == list.size){
                MyNode last = list.removeLast();
                map.remove(last.key);
            }
        }
        list.addFirst(cur);
        map.put(key, cur);
    }
}

上面的代码和思路主要借鉴了LRU 策略详解和实现 这篇文章。

你可能感兴趣的:(Java算法,面试,java,数据结构,算法)