zoukankan      html  css  js  c++  java
  • 手写一个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());
        }
    }

    验证结果如下:

  • 相关阅读:
    自定义组件要加@click方法
    绑定样式
    647. Palindromic Substrings
    215. Kth Largest Element in an Array
    448. Find All Numbers Disappeared in an Array
    287. Find the Duplicate Number
    283. Move Zeroes
    234. Palindrome Linked List
    202. Happy Number
    217. Contains Duplicate
  • 原文地址:https://www.cnblogs.com/rmxd/p/15223588.html
Copyright © 2011-2022 走看看