zoukankan      html  css  js  c++  java
  • LRU缓存算法的实现

    LRU

    LRU(Least Recently Used)是一种常见的页面置换算法,在计算中,所有的文件操作都要放在内存中进行,然而计算机内存大小是固定的,所以我们不可能把所有的文件都加载到内存,因此我们需要制定一种策略对加入到内存中的文件进项选择。

    常见的页面置换算法有如下几种:

    • LRU 最近最久未使用
    • FIFO 先进先出置换算法 类似队列
    • OPT 最佳置换算法 (理想中存在的)
    • NRU Clock置换算法
    • LFU 最少使用置换算法
    • PBA 页面缓冲算法

    LRU原理

    LRU的设计原理就是,当数据在最近一段时间经常被访问,那么它在以后也会经常被访问。这就意味着,如果经常访问的数据,我们需要然其能够快速命中,而不常访问的数据,我们在容量超出限制内,要将其淘汰。

    当我们的数据按照如下顺序进行访问时,LRU的工作原理如下:

    正如上面图所表示的意思:每次访问的数据都会放在栈顶,当访问的数据不在内存中,且栈内数据存储满了,我们就要选择移除栈底的元素,因为在栈底部的数据访问的频率是比较低的。所以要将其淘汰。

    LRU的实现

    如何来设计一款LRU算法呢?对于这种类似序列的结构我们一般可以选择链表或者是数组来构建。

    差异对比:

    • 数组 查询比较快,但是对于增删来说是一个不是一个好的选择
    • 链表 查询比较慢,但是对于增删来说十分方便O(1)时间复杂度内搞定

    有没有办法既能够让其搜索快,又能够快速进行增删操作。
    我们可以选择链表+hash表,hash表的搜索可以达到0(1)时间复杂度,这样就完美的解决我们搜索时间慢的问题了

    1. 基于链表+Hash表

    Hash表,在Java中HashMap是我们的不二选择
    链表,Node一个双向链表的实现,Node中存放的是数结构如下:

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

    我们通过HashMap中key存储Node的key,value存储Node来建立Map对Node的映射关系。我们将HashMap看作是一张检索表,我们可以可以快速的检索到我们需要定位的Node

    下图展示这个结构:

    代码实现

    大致思路:

    1. 构建双向链表节点ListNode,应包含key,value,prev,next这几个基本属性

    2. 对于Cache对象来说,我们需要规定缓存的容量,所以在初始化时,设置容量大小,然后实例化双向链表的head,tail,并让head.next->tail tail.prev->head,这样我们的双向链表构建完成

    3. 对于get操作,我们首先查阅hashmap,如果存在的话,直接将Node从当前位置移除,然后插入到链表的首部,在链表中实现删除直接让node的前驱节点指向后继节点,很方便.如果不存在,那么直接返回Null

    4. 对于put操作,比较麻烦。

    开始检测hash表存在?插入到链表首部结束是否超出缓存容量?移除hash表key,移除链表尾部node创建node,加入链表首部,记录hash表yesnoyesno
    package code.fragment;
    
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    public class LRUCache<V> {
    
        /**
         * 容量
         */
        private int capacity = 1024;
        /**
         * Node记录表
         */
        private Map<String, ListNode<String, V>> table = new ConcurrentHashMap<>();
        /**
         * 双向链表头部
         */
        private ListNode<String, V> head;
        /**
         * 双向链表尾部
         */
        private ListNode<String, V> tail;
    
    
        public LRUCache(int capacity) {
            this();
            this.capacity = capacity;
        }
    
    
        public LRUCache() {
            head = new ListNode<>();
            tail = new ListNode<>();
            head.next = tail;
            head.prev = null;
            tail.prev = head;
            tail.next = null;
        }
    
    
        public V get(String key) {
    
            ListNode<String, V> node = table.get(key);
            //如果Node不在表中,代表缓存中并没有
            if (node == null) {
                return null;
            }
            //如果存在,则需要移动Node节点到表头
    
    
            //截断链表,node.prev -> node  -> node.next ====> node.prev -> node.next
            //         node.prev <- node <- node.next  ====>  node.prev <- node.next
            node.prev.next = node.next;
            node.next.prev = node.prev;
    
            //移动节点到表头
            node.next = head.next;
            head.next.prev = node;
            node.prev = head;
            head.next = node;
            //存在缓存表
            table.put(key, node);
            return node.value;
        }
    
    
        public void put(String key, V value) {
            ListNode<String, V> node = table.get(key);
            //如果Node不在表中,代表缓存中并没有
            if (node == null) {
                if (table.size() == capacity) {
                    //超过容量了 ,首先移除尾部的节点
                    table.remove(tail.prev.key);
                    tail.prev = tail.next;
                    tail.next = null;
                    tail = tail.prev;
    
                }
                node = new ListNode<>();
                node.key = key;
                node.value = value;
                table.put(key, node);
            }
            //如果存在,则需要移动Node节点到表头
            node.next = head.next;
            head.next.prev = node;
            node.prev = head;
            head.next = node;
    
    
        }
    
        /**
         * 双向链表内部类
         */
        public static class ListNode<K, V> {
            private K key;
            private V value;
            ListNode<K, V> prev;
            ListNode<K, V> next;
    
            public ListNode(K key, V value) {
                this.key = key;
                this.value = value;
            }
    
    
            public ListNode() {
    
            }
        }
    
    
        public static void main(String[] args) {
            LRUCache<ListNode> cache = new LRUCache<>(4);
            ListNode<String, Integer> node1 = new ListNode<>("key1", 1);
            ListNode<String, Integer> node2 = new ListNode<>("key2", 2);
            ListNode<String, Integer> node3 = new ListNode<>("key3", 3);
            ListNode<String, Integer> node4 = new ListNode<>("key4", 4);
            ListNode<String, Integer> node5 = new ListNode<>("key5", 5);
            cache.put("key1", node1);
            cache.put("key2", node2);
            cache.put("key3", node3);
            cache.put("key4", node4);
            cache.get("key2");
            cache.put("key5", node5);
            cache.get("key2");
        }
    }
    

    断点执行情况:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    =========================

    工作机会(内部推荐):发送邮件至gaoyabing@126.com,看到会帮转内部HR。

    邮件标题:X姓名X_X公司X_简历(如:张三_东方财富_简历),否则一律垃圾邮件!

    公司信息:

    1. 1.东方财富|上海徐汇、南京|微信客户端查看职位(可自助提交信息,微信打开);
  • 相关阅读:
    session中删除数组中的某一个值
    Windows 查看端口占用进程并关闭
    Debian 9 / Debian 10 / Ubuntu 18.04 / Ubuntu 18.10快速开启BBR加速 或 关闭BBR加速
    在ASP.NET Web API 2中使用Owin OAuth 刷新令牌(示例代码)
    在ASP.NET Web API 2中使用Owin基于Token令牌的身份验证
    Web API 2 的操作结果
    WebApi接口安全性 接口权限调用、参数防篡改防止恶意调用
    关于EF中使用Migrations的一些小提示
    Entity Framework 6 多对多增改操作指南
    用MVC5+EF6+WebApi 做一个考试功能(六) 仓储模式 打造EF通用仓储类
  • 原文地址:https://www.cnblogs.com/Chary/p/15464930.html
Copyright © 2011-2022 走看看