<aside> 💡 LRU 缓存机制可以通过哈希表+双向链表实现,用一个哈希表和一个双向链表维护所有在缓存中的键值对。

• 双向链表按照被使用的顺序存储了这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的。 • 哈希表即为普通的哈希映射(HashMap),通过缓存数据的键映射到其在双向链表中的位置。

这样以来,首先使用哈希表进行定位,找出缓存项在双向链表中的位置,随后将其移动到双向链表的头部,即可在 O(1)O(1) 的时间内完成 get 或者 put 操作。

作者:LeetCode-Solution 链接:https://leetcode-cn.com/problems/lru-cache/solution/lruhuan-cun-ji-zhi-by-leetcode-solution/ 来源:力扣(LeetCode)

</aside>

class LRUCache {
    class DLinkedNode {
        int key;
        int value;
        DLinkedNode prev;
        DLinkedNode next;

        public DLinkedNode() {
        }

        public DLinkedNode(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    private Map<Integer, DLinkedNode> cache = new HashMap<>();
    // 最大容量
    private int capacity;
    // 当前容量
    private int size;
    // 伪节点
    private DLinkedNode head, tail;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.size = 0;
        this.head = new DLinkedNode();
        this.tail = new DLinkedNode();
        head.next = tail;
        tail.prev = head;
    }

    /**
     * 删除node
     */
    private void removeNode(DLinkedNode node) {
        // 把前节点的next指向后节点
        node.prev.next = node.next;
        // 把后节点的prev指向前节点
        node.next.prev = node.prev;
        // 把当前节点的prev和next置空
        node.prev = null;
        node.next = null;
    }

    /**
     * 把node添加到头部
     */
    private void addToHead(DLinkedNode node) {
        // 把node prev 与 next 挂到双链表上
        node.prev = head;
        node.next = head.next;
        // 修改head的next为node
        this.head.next = node;
        // 修改后节点的prev为node
        node.next.prev = node;
    }

    /**
     * 把node移动到头部
     */
    private void moveToHead(DLinkedNode node) {
        removeNode(node);
        addToHead(node);
    }

    /**
     * 删除链表最后一个节点
     */
    private DLinkedNode removeTail() {
        DLinkedNode node = tail.prev;
        if (node == head) {
            return null;
        }
        removeNode(node);
        return node;
    }

    public int get(int key) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            return -1;
        }
        // 如果存在, 则移动到头部
        moveToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        DLinkedNode node = cache.get(key);
        // 如果存在, 则修改value并移动到头部
        if (node != null) {
            node.value = value;
            moveToHead(node);
            return;
        }
        // 如果不存在, 则新增node
        DLinkedNode newNode = new DLinkedNode(key, value);
        // 添加进hash表
        cache.put(key, newNode);
        // 添加进链表
        addToHead(newNode);
        size++;
        // 如果超出容量, 则删除尾部节点
        if (size > capacity) {
            DLinkedNode tail = removeTail();
            if (tail == null) {
                return;
            }
            // 删除hash表中node
            cache.remove(tail.key);
            size--;
        }
    }
}