146. LRU 缓存机制[中等]

170 阅读2分钟

​问题

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

LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存

int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。

void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

思路

用Map去存储数据,再加一个双向链表的辅助结构,链表的头部总是最近访问的,当超过容量之后总是从尾部删除节点。需要注意的是:如果连续put相同key的不同value,需要更新value值,而且还要调整该节点到链表的头部。

代码一

可以直接使用Java的LinkedHashMap实现。

public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int capacity;
    private final LRUCacheListener<K, V> listener;
​
    public LRUCache(int capacity) {
        this(capacity, 0.75F, null);
    }
​
    public LRUCache(int capacity, LRUCacheListener<K, V> listener) {
        this(capacity, 0.75F, listener);
    }
​
    public LRUCache(int capacity, float loadFactor, LRUCacheListener<K, V> listener) {
        // 由于总是先将数据put进去,然后移除,所以有可能会有扩容操作,capacity+1防止动态扩容。
        super((int) Math.ceil(capacity / loadFactor) + 1, loadFactor, true);
        this.capacity = capacity;
        this.listener = listener;
    }
​
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        boolean remove = size() > capacity;
        // 每次put操作都会调用removeEldestEntry,所以在processEldest之前判断容量是否满了。
        if (listener != null && remove) {
            listener.processEldest(eldest);
        }
        return remove;
    }
}
public interface LRUCacheListener<K, V> {
    public void processEldest(Map.Entry<K, V> eldest);
}

代码二

public class LRUCache<K, V> {
    private final Map<K, ListNode<K, V>> map;
    private final ListNode<K, V> head;
    private final ListNode<K, V> tail;
    private final int capacity;
​
    public LRUCache(int capacity) {
        this.capacity = capacity;
        map = new HashMap<>(capacity);
        head = new ListNode<>(null, null);
        tail = new ListNode<>(null, null);
        head.next = tail;
        tail.prev = head;
    }
​
    public void put(K key, V value) {
        ListNode<K, V> node = map.get(key);
        if (node != null) {
            node.val = value;
            // 此处直接通过get方法调整
            get(key);
        } else {
            node = new ListNode<>(key, value);
            map.put(key, node);
            node.next = head.next;
            head.next.prev = node;
            head.next = node;
            node.prev = head;
            if (map.size() > capacity) {
                ListNode<K, V> removed = tail.prev;
                map.remove(removed.key);
                removed.prev.next = tail;
                tail.prev = removed.prev;
            }
        }
​
    }
​
    public V get(K key) {
        ListNode<K, V> node = map.get(key);
        if (node != null) {
            node.prev.next = node.next;
            node.next.prev = node.prev;
​
            node.next = head.next;
            head.next.prev = node;
​
            head.next = node;
            node.prev = head;
        }
        return node == null ? null : node.val;
    }
​
    private static class ListNode<K, V> {
        private final K key;
        private V val;
        private ListNode<K, V> next;
        private ListNode<K, V> prev;
​
        public ListNode(K key, V val) {
            this.key = key;
            this.val = val;
        }
​
        @Override
        public String toString() {
            return "ListNode{" +
                    "key=" + key +
                    ", val=" + val +
                    '}';
        }
    }
​
    @Override
    public String toString() {
        return "LRUCache{" +
                "map=" + map +
                '}';
    }
}

复杂度

时间复杂度:O(1)

空间复杂度:O(capacity)

硬广告

欢迎关注公众号:double6

final

本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情