Java 实现 LRU 缓存机制

1,591 阅读2分钟

LeetCode 146. LRU 缓存机制

运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制 。

实现 LRUCache 类:

  • LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
  • void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则 插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

进阶: 你是否可以在 O(1) 时间复杂度内完成这两种操作?

分析

LRUCache 类实现后需要满足的条件:

  1. O(1) 时间复杂度完成 get & put 操作
  2. get 完成时,需要将 get 的数据设置为最新
  3. put 数据时,当超出容量时,需要将最久未使用的数据删除,并将 put 的数据设置为最新

由上可得:

  1. 通过链表来标识出数据的新旧关系,新的数据放到后面即可
  2. 需要 O(1) 的时间复杂度完成操作,则还需要使用哈希表来存储数据
  3. 设置数据为最新则可以通过先删除数据,然后再添加数据到末尾来实现
  4. 为了实现快速删除数据,我们需要使用双向链表

具体实现

存储数据的节点 Node:

class Node {
    public int key, val;
    public Node prev, next;
    public Node(int k, int v){
        this.key = k;
        this.val = v;
    }
}

用户实现快速操作的 Map:

HashMap<Integer, Node> map;

用于实现 LRU 的双向链表:

class DoubleList {
    private Node head, tail;
    private int size;

    public DoubleList() {
    }

    public void addLast(Node node) {
    }

    public void remove(Node node) {
    }

    public Node removeFirst() {
    }

    public int size() {
    }
}

LRUCache 具体实现:

import java.util.HashMap;

public class LRUCache {
    private int cap;
    private HashMap<Integer, Node> map;
    public DoubleList cache;

    public LRUCache(int capacity) {
        this.cap = capacity;
        map = new HashMap();
        cache = new DoubleList();
    }

    class Node {
        public int key, val;
        public Node prev, next;
        public Node(int k, int v){
            this.key = k;
            this.val = v;
        }
    }

    class DoubleList {
        private Node head, tail;
        private int size;

        public DoubleList() {
            head = new Node(-1, -1);
            tail = new Node(-1, -1);
            head.next = tail;
            tail.prev = head;
            size = 0;
        }

        public void addLast(Node node) {
            tail.prev.next = node;
            node.prev = tail.prev;
            node.next = tail;
            tail.prev = node;
            size++;
        }

        public void remove(Node node) {
            node.prev.next = node.next;
            node.next.prev = node.prev;
            map.remove(node.key);
            size--;
        }

        public Node removeFirst() {
            if (head.next == tail) {
                return null;
            }
            Node first = head.next;
            remove(first);
            return first;
        }

        public int size() {
            return this.size;
        }
    }

    public Integer get(int key) {
        if (!map.containsKey(key)) {
            return -1;
        }
        Node node = map.get(key);
        cache.remove(node);
        cache.addLast(node);
        return map.get(key).val;
    }

    public void put(int key, int val) {
        Node node = new Node(key, val);
        if (map.containsKey(key)) {
            map.put(key, node);
            cache.remove(node);
        } else {
            if (this.cap > cache.size()) {
                map.put(key, node);
            } else {
                cache.removeFirst();
            }
        }
        cache.addLast(node);
    }

    public static void main(String[] args) {
        LRUCache cache = new LRUCache(3);
        cache.put(0,0);
        cache.put(1,1);
        cache.put(2,2);
        cache.put(3,3);
        System.out.println(cache.get(0));
        Node p = cache.cache.head;
        System.out.println("------cache-------");
        while (p!= null) {
            System.out.println(p.val);
            p = p.next;
        }
    }
}

使用 LinkedHashMap 实现

Java 集合提供 LinkedHashMap 的底层和上述实现类似,可以直接被用来实现 LRU:

import java.util.LinkedHashMap;

public class LRUCache1 {
    private int capacity;
    private LinkedHashMap<Integer, Integer> cache;

    public LRUCache1(int capacity) {
        this.capacity = capacity;
        cache = new LinkedHashMap<>();
    }

    public Integer get(int key) {
        if (!cache.containsKey(key)) {
            return -1;
        } else {
            Integer val = cache.get(key);
            cache.remove(key);
            cache.put(key, val);
            return val;
        }
    }

    public void put(int key, int val) {
        if (cache.containsKey(key)) {
            cache.remove(key);
        }
        if (cache.size() >= capacity) {
            Integer first = cache.keySet().iterator().next();
            cache.remove(first);
        }
        cache.put(key, val);
    }

    public static void main(String[] args) {
        LRUCache1 cache = new LRUCache1(2);
        cache.put(1,1);
        cache.put(2,2);
        cache.put(3,3);
        System.out.println(cache.get(1));
    }
}