手写一个LRU缓存机制

  前言:不断学习就是程序员的宿命

此题对应力扣题目地址:https://leetcode-cn.com/problems/lru-cache/

  

一、LRU介绍

   LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的数据予以淘汰。

二、设计思想

  1、所谓缓存,必须要有读+写操作,按照命中率的思路考虑,写操作+读操作时间复杂度都需要为O(1)

  2、特性要求

    a.必须要有顺序之分,区分最近使用的和很久没有使用的数据排序

    b.读和写操作一次搞定

    c.如果容量满了要删除最不常用的数据,每次新访问的还要把新的数据插入到队头

综上所述:查找快、插入快、删除快、且还要先后排序----?什么样的数据结构可以满足这个问题呢?且时间复杂度为O(1)完成操作,你觉得什么样的数据结构合适呢?

答案:LRU的算法核心就是哈希链表(哈希可以保证时间复杂度为O(1)、链表可以保证插入和删除快)

本质就是:HashMap+DoubleLinkedList,时间复杂度是O(1),哈希表+双向链表的结合体

三、LRU实操

1、封装双向链表节点Node作为数据载体

  可参考JDK源码:AQS抽象队列同步器内部Node(封装Thread等信息)、HashMap内部节点Node  

 

   class Node<K, V> {
        K key;
        V value;
        Node<K, V> prev;
        Node<K, V> next;

        public Node() {
            this.prev = this.next = null;
        }

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
            this.prev = this.next = null;
        }
    }

 

2、双向链表构造方法

    class DoubleLinkedList<K, V> {
        Node<K, V> head;//头节点
        Node<K, V> tail;//尾节点

        public DoubleLinkedList() {
            //头尾哨兵节点
            this.head = new Node<K, V>();
            this.tail = new Node<K, V>();
            this.head.next = this.tail;
            this.tail.prev = this.head;
        }
    }

 

  执行完成构造方法以后此时双向链表结构如下:

 

 

 3、双向链表添加节点方法

class DoubleLinkedList<K, V> {
        Node<K, V> head;//头节点
        Node<K, V> tail;//尾节点

        public DoubleLinkedList() {
            //头尾哨兵节点
            this.head = new Node<K, V>();
            this.tail = new Node<K, V>();
            this.head.next = this.tail;
            this.tail.prev = this.head;
        }

        /**
         * 添加节点
         */
        public void add(Node<K, V> node) {
            node.next = head.next;
            node.prev = head;
            head.next.prev = node;
            head.next = node;
        }
    }

  执行添加节点操作如下:

  

  执行完添加方法以后此时双向链表结构如下:

 

 

 4、双向链表删除节点

    /**
         * 删除节点
         */
        public void remove(Node<K, V> node) {
            node.next.prev = node.prev;
            node.prev.next = node.next;
            node.prev = null;
            node.next = null;
        }

 

删除节点操作如下:

 

5、获取最后一个结点

 /**
         * 获取最后一个节点
         * 将来用作最久不使用的数据
         * @return
         */
        public Node<K, V> getLast() {
            return this.tail.prev;
        }

6、完整代码

public class LRUCache {
    //容量
    private int capacity;
    //map用于查找 时间复杂度O(1)
    private Map<Integer, Node<Integer, Integer>> map;
    //双向链表
    private DoubleLinkedList<Integer, Integer> doubleLinkedList;
    //构造
    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.map = new HashMap<>();
        this.doubleLinkedList = new DoubleLinkedList<>();
    }

    public Integer get(int key) {
        if (map.containsKey(key)) {
            //查找对应元素 时间复杂度O(1)
            Node<Integer, Integer> node = map.get(key);
            //使用一次就删除
            this.doubleLinkedList.remove(node);
            //重新放在头结点位置(认为是最近使用的)
            this.doubleLinkedList.add(node);
            return node.value;
        }
        return -1;
    }

    public void put(int key, int value) {
        if (map.containsKey(key)) {
            //如果存在则进行更新
            Node<Integer, Integer> node = map.get(key);
            node.value = value;
            map.put(key, node);
            //更新完成以后放至头结点位置,认为是最近使用的
            this.doubleLinkedList.remove(node);
            this.doubleLinkedList.add(node);
        } else {
            //如果不存在则进行判断容量是否达上限
            if (map.size() == this.capacity) {
                //最后一个节点即最近最少使用的
                Node<Integer, Integer> lastNode = this.doubleLinkedList.getLast();
                map.remove(lastNode.key);
                //删除最近最少使用的
                doubleLinkedList.remove(lastNode);
            }
            Node<Integer, Integer> node = new Node<>();
            node.key = key;
            node.value = value;
            map.put(key, node);
            //添加至头结点位置,认为是最近刚刚使用的
            this.doubleLinkedList.add(node);
        }

    }

    //map负责查找,构建一个虚拟的双向链表,里面为一个个Node,作为数据载体
    //双向链表节点
    class Node<K, V> {
        K key;
        V value;
        Node<K, V> prev;
        Node<K, V> next;

        public Node() {
            this.prev = this.next = null;
        }

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
            this.prev = this.next = null;
        }
    }

    class DoubleLinkedList<K, V> {
        Node<K, V> head;//头节点
        Node<K, V> tail;//尾节点

        public DoubleLinkedList() {
            //头尾哨兵节点
            this.head = new Node<K, V>();
            this.tail = new Node<K, V>();
            this.head.next = this.tail;
            this.tail.prev = this.head;
        }

        /**
         * 添加节点
         */
        public void add(Node<K, V> node) {
            node.next = head.next;
            node.prev = head;
            head.next.prev = node;
            head.next = node;
        }

        /**
         * 删除节点
         */
        public void remove(Node<K, V> node) {
            node.next.prev = node.prev;
            node.prev.next = node.next;
            node.prev = null;
            node.next = null;
        }

        /**
         * 获取最后一个节点
         * 将来用作最久不使用的数据
         *
         * @return
         */
        public Node<K, V> getLast() {
            return this.tail.prev;
        }
    }

    public static void main(String[] args) {
        LRUCache lruCache = new LRUCache(3);
        lruCache.put(1,1);
        lruCache.put(2,2);
        lruCache.put(3,3);
        System.out.println(lruCache.map.keySet());
        lruCache.put(4,4);
        System.out.println(lruCache.map.keySet());

        lruCache.put(3,1);
        System.out.println(lruCache.map.keySet());
        lruCache.put(3,1);
        System.out.println(lruCache.map.keySet());
        lruCache.put(3,1);
        System.out.println(lruCache.map.keySet());
        lruCache.put(5,1);
        System.out.println(lruCache.map.keySet());
    }
}

验证结果如下:

 

posted @ 2021-09-03 16:14  coder、  阅读(117)  评论(0编辑  收藏  举报