LRU Cache原理和实现

434 阅读3分钟
  • 构造一个LRU Cache的数据结构,它应该有一下两个方法:getset
    • get(key): 如果Cache中Key存在,则返回它的值(假设为正数),否则返回-1.
    • set(key, value) :设置新值;如果Cache中key存在,则插入新的value。如果Cache达到它到capacity,在插入一个新项之前应该删除最久不使用的项。
  • 举例:
// Let’s say we have a LRU cache of capacity 2. 
LRUCache cache = new LRUCache(2);
cache.set(1, 10); // it will store a key (1) with value 10 in the cache. 
cache.set(2, 20); // it will store a key (2) with value 20 in the cache. 
cache.get(1); // returns 10 
cache.set(3, 30); // evicts key 2 and store a key (3) with value 30 in the cache. 
cache.get(2); // returns -1 (not found) 
cache.set(4, 40); // evicts key 1 and store a key (4) with value 40 in the cache. 
cache.get(1); // returns -1 (not found) 
cache.get(3); // returns 30 
cache.get(4); // returns 40

- 解答

  1. 暴力方法
    假定我们有一个存储Node的数组,每个Node包含以下信息
class Node {
    int key;
    int value;
 
    // it shows the time at which the key is stored.
    // We will use the timeStamp to find out the
    // least recently used (LRU) node.
    int timeStamp;
 
    public Node(int key, int value)
    {
        this.key = key;
        this.value = value;
 
        // currentTimeStamp from system
        this.timeStamp = currentTimeStamp;
    }
}

数组的大小就是Cache到容量capacity。

  • get(int key): 遍历一遍,如果存在则返回对应到value,否则返回-1。 时间复杂度:O(n)
  • set(int key, int value):如果数组已经满了,我们需要从数组中删除一个元素,为了找到满足LRU到节点Node,我们遍历一遍,通过时间戳timeStamp找到最旧的那个值。然后就可以插入新增到节点Node。如果数组未满,我们直接插入新增到节点Node。 时间复杂度:O(n)
  1. 最优方法
    解决这个问题到关键是使用双端队列,因为它可以快速的移动节点Nodes。
    LRU Cache是一个键值对为Key-Node的HashMap,HashMap可以使get()的时间复杂度是O(1)。双端队列可以是Node的操作:adding/removal到时间复杂度是O(1)。
import java.util.HashMap;
 
class Node {
    int key;
    int value;
    Node pre;
    Node next;
 
    public Node(int key, int value)
    {
        this.key = key;
        this.value = value;
    }
}
 
class LRUCache {
    private HashMap<Integer, Node> map;
    private int capacity, count;
    private Node head, tail;
 
    public LRUCache(int capacity)
    {
        this.capacity = capacity;
        map = new HashMap<>();
        head = new Node(0, 0);
        tail = new Node(0, 0);
        head.next = tail;
        tail.pre = head;
        head.pre = null;
        tail.next = null;
        count = 0;
    }
 
    public void deleteNode(Node node)
    {
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }
 
    public void addToHead(Node node)
    {
        node.next = head.next;
        node.next.pre = node;
        node.pre = head;
        head.next = node;
    }
 
    // This method works in O(1)
    public int get(int key)
    {
        if (map.get(key) != null) {
            Node node = map.get(key);
            int result = node.value;
            deleteNode(node);
            addToHead(node);
            System.out.println("Got the value : " + result
                               + " for the key: " + key);
            return result;
        }
        System.out.println("Did not get any value"
                           + " for the key: " + key);
        return -1;
    }
 
    // This method works in O(1)
    public void set(int key, int value)
    {
        System.out.println("Going to set the (key, "
                           + "value) : (" + key + ", "
                           + value + ")");
        if (map.get(key) != null) {
            Node node = map.get(key);
            node.value = value;
            deleteNode(node);
            addToHead(node);
        }
        else {
            Node node = new Node(key, value);
            map.put(key, node);
            if (count < capacity) {
                count++;
                addToHead(node);
            }
            else {
                map.remove(tail.pre.key);
                deleteNode(tail.pre);
                addToHead(node);
            }
        }
    }
}
 
public class TestLRUCache {
    public static void main(String[] args)
    {
        System.out.println("Going to test the LRU "
                           + " Cache Implementation");
        LRUCache cache = new LRUCache(2);
 
        // it will store a key (1) with value
        // 10 in the cache.
        cache.set(1, 10);
 
        // it will store a key (1) with value 10 in the
        // cache.
        cache.set(2, 20);
        System.out.println("Value for the key: 1 is "
                           + cache.get(1)); // returns 10
 
        // evicts key 2 and store a key (3) with
        // value 30 in the cache.
        cache.set(3, 30);
 
        System.out.println(
            "Value for the key: 2 is "
            + cache.get(2)); // returns -1 (not found)
 
        // evicts key 1 and store a key (4) with
        // value 40 in the cache.
        cache.set(4, 40);
        System.out.println(
            "Value for the key: 1 is "
            + cache.get(1)); // returns -1 (not found)
        System.out.println("Value for the key: 3 is "
                           + cache.get(3)); // returns 30
        System.out.println("Value for the key: 4 is "
                           + cache.get(4)); // return 40
    }
}

输出:

Going to test the LRU  Cache Implementation
Going to set the (key, value) : (1, 10)
Going to set the (key, value) : (2, 20)
Got the value : 10 for the key: 1
Value for the key: 1 is 10
Going to set the (key, value) : (3, 30)
Did not get any value for the key: 2
Value for the key: 2 is -1
Going to set the (key, value) : (4, 40)
Did not get any value for the key: 1
Value for the key: 1 is -1
Got the value : 30 for the key: 3
Value for the key: 3 is 30
Got the value : 40 for the key: 4
Value for the key: 4 is 40

另外一种实现方式: 使用LinkedHashMap。
removeEldestEntry()被重写,目的是当大小超过容量时,需要移除旧的Mapping。

import java.util.LinkedHashMap;
import java.util.Map;
 
class LRUCache {
    private LinkedHashMap<Integer, Integer> map;
    private final int CAPACITY;
    public LRUCache(int capacity)
    {
        CAPACITY = capacity;
        map = new LinkedHashMap<Integer, Integer>(capacity, 0.75f, true) {
            protected boolean removeEldestEntry(Map.Entry eldest)
            {
                return size() > CAPACITY;
            }
        };
    }
 
    // This method works in O(1)
    public int get(int key)
    {
        System.out.println("Going to get the value " +
                               "for the key : " + key);
        return map.getOrDefault(key, -1);
    }
 
    // This method works in O(1)
    public void set(int key, int value)
    {
        System.out.println("Going to set the (key, " +
             "value) : (" + key + ", " + value + ")");
        map.put(key, value);
    }
}
 
public class TestLRUCacheWithLinkedHashMap {
 
    public static void main(String[] args)
    {
        System.out.println("Going to test the LRU "+
                           " Cache Implementation");
        LRUCache cache = new LRUCache(2);
  
        // it will store a key (1) with value
        // 10 in the cache.
        cache.set(1, 10);
 
        // it will store a key (1) with value 10 in the cache.
        cache.set(2, 20);
        System.out.println("Value for the key: 1 is " +
                           cache.get(1)); // returns 10
 
        // evicts key 2 and store a key (3) with
        // value 30 in the cache.
        cache.set(3, 30);
 
        System.out.println("Value for the key: 2 is " +
                cache.get(2)); // returns -1 (not found)
 
        // evicts key 1 and store a key (4) with
        // value 40 in the cache.
        cache.set(4, 40);
        System.out.println("Value for the key: 1 is " +
               cache.get(1)); // returns -1 (not found)
        System.out.println("Value for the key: 3 is " +
                           cache.get(3)); // returns 30
        System.out.println("Value for the key: 4 is " +
                           cache.get(4)); // return 40
 
    }
}

输出:

Going to test the LRU  Cache Implementation
Going to set the (key, value) : (1, 10)
Going to set the (key, value) : (2, 20)
Going to get the value for the key : 1
Value for the key: 1 is 10
Going to set the (key, value) : (3, 30)
Going to get the value for the key : 2
Value for the key: 2 is -1
Going to set the (key, value) : (4, 40)
Going to get the value for the key : 1
Value for the key: 1 is -1
Going to get the value for the key : 3
Value for the key: 3 is 30
Going to get the value for the key : 4
Value for the key: 4 is 40
参考文章

Design a data structure for LRU Cache