LRU缓存的设计

LRU缓存设计

什么是LRU

  • 概念
    • LRU的初始设计与应用是在操作系统中的虚拟存储模块上,它作为操作系统的虚拟页面置换的算法,得到了广泛的应用。
  • 特点与应用
    • 由于操作系统的页表大小有限,页面项的数量也有限,当我们访问某个物理块的时候,可能产生缺页中断,这个时候操作系统就会触发调页请求,从外存置换对应的物理页进入内存,提供访问。而当页表已经存满对应的物理页映射项的情况下,仍然发生中断请求,这个时候就需要一种页面置换算法,将外存的物理页换入内存,淘汰久的页面。
    • LRU利用程序的局部性原理,对最近访问的页面进行更新,淘汰最久未使用的页面。
  • 应用场景
    • LRU的置换思想,同样可以应用到缓存的技术中。接下来,我们将设计一个高性能的LRU缓存。

LRU缓存设计

  • 参数
    • Size缓存大小
    • 是一种K-V结构
    • get(K key)和put(K key,V val)
  • 性能要求
    • O(1)的读写效率

设计步骤

  • 选择合适的数据结构

首先我们进行了分析,K-V结构最常用的就是我们的hashMap,用hashMap存取数据,可以达到O(1)。

  • 存在问题
    • 用hashMap解决O(1)的读写效率,但是如何能够用O(1)进行置换呢?
  • 分析
    • 读可以用O(1)解决,但是写不行,在容量已满的情况下,写缓存必然导致数据的置换,如何能够用O(1)进行数据置换呢?
  • 解决方案
    • 选择哈希双链表数据结构,如下:

在这里插入图片描述

代码实现

public class LRUCache<K, V> {
    private int size;
    /**
     * virtual nodes
     */
    private Node tail, head;
    private int capacity;

    private final Map<K, Node> cache = new HashMap<>();


    public LRUCache(int capacity) {
        if (capacity < 1) {
            throw new IllegalArgumentException("capacity can not be < 1");
        }
        tail = new Node(null, null);
        head = new Node(null, null);
        head.next = tail;
        tail.prev = head;
        this.size = 0;
        this.capacity = capacity;
    }

    public V get(K key) {
        if (!cache.containsKey(key)) {
            return null;
        } else {
            V val = cache.get(key).val;
            //调用put更新即可
            put(key, val);
            return val;
        }
    }

    public void put(K key, V val) {
        //构造新节点
        Node newNode = new Node(key, val);
        if (cache.containsKey(key)) {
            //remove old
            remove(cache.get(key));
            //add new
            addFirst(newNode);
            //更新map
            cache.put(key, newNode);
        } else {
            if (capacity == size()) {
                Node last = removeLast();
                cache.remove(last.key);
            }
            //添加到头部
            addFirst(newNode);
            cache.put(key, newNode);
        }
    }

    private void addFirst(Node node) {
        if (node != null) {
            node.next = head.next;
            node.prev = head;
            head.next.prev = node;
            head.next = node;
            size++;
        }
    }

    private void remove(Node node) {
        if (node != null) {
            node.prev.next = node.next;
            node.next.prev = node.prev;
            size--;
        }
    }

    private Node removeLast() {
        if (tail.prev == head) {
            return null;
        }
        Node last = tail.prev;
        remove(last);
        return last;
    }

    public int size() {
        return size;
    }

    public void print() {
        for (Map.Entry e : cache.entrySet()
                ) {
            System.out.println(e.getKey() + ":" + ((Node) e.getValue()).val);
        }
    }

    class Node {
        K key;
        V val;
        Node next, prev;

        public Node(K key, V val) {
            this.key = key;
            this.val = val;
        }
    }

}
发布了57 篇原创文章 · 获赞 32 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/rekingman/article/details/103932145
今日推荐