zoukankan      html  css  js  c++  java
  • LRU算法与代码实现

    场景

    公司的业务越来越复杂,我们需要抽出一个用户系统,向各个业务系统提供用户的基本信息。

    用户系统作为非常基础的应用,公司内部会有很多个系统去调用,因此一定要注意性能问题。因此在用户系统中,可以增加一个内存缓存,当然具体的信息是存放在数据库里的。每当查找一个用户时会先在哈希表中进行查询,以此来提高访问的性能。

    不过把缓存数据放在内存也会导致一个问题,就是时间长了之后,内存可能会由于数据太多而溢出,那么这时候就需要有缓存清除机制来保证系统正常运行。

    LRU算法

    LRU全称Least Recently Used,也就是最近最少使用的意思,是一种内存管理算法,该算法最早应用于Linux操作系统。
    这个算法基于一种假设:长期不被使用的数据,在未来被用到的几率也不大。因此,当数据所占内存达到一定阈值时,我们要移除掉最近最少被使用的数据。
    在LRU算法中,使用了一种有趣的数据结构,这种数据结构叫作哈希链表。

    哈希链表

    哈希表是由若干个Key-Value组成的。在“逻辑”上,这些Key-Value是无所谓排列顺序的,谁先谁后都一样。

    在哈希链表中,这些Key-Value不再是彼此无关的存在,而是被一个链条串了起来。每一个Key-Value都具有它的前驱Key-Value、后继Key-Value,就像双向链表中的节点一样。

    这样一来,原本无序的哈希表就拥有了固定的排列顺序。

    LRU算法演示

    1. 假设使用哈希链表来缓存用户信息,目前缓存了4个用户,这4个用户是按照被访问的时间顺序依次从链表右端插入的。

    2. 如果这时业务方访问用户5,由于哈希链表中没有用户5的数据,需要从数据库中读取出来,插入到缓存中。此时,链表最右端是最新被访问的用户5,最左端是最近最少被访问的用户1。

    3. 接下来,如果业务方访问用户2,哈希链表中已经存在用户2的数据,这时我们把用户2从它的前驱节点和后继节点之间移除,重新插入链表的最右端。此时,链表的最右端变成了最新被访问的用户2,最左端仍然是最近最少被访问的用户1。

    4. 接下来,如果业务方请求修改用户4的信息。同样的道理,我们会把用户4从原来的位置移动到链表的最右侧,并把用户信息的值更新。这时,链表的最右端是最新被访问的用户4,最左端仍然是最近最少被访问的用户1。

    5. 后来业务方又要访问用户6,用户6在缓存里没有,需要插入哈希链表中。假设这时缓存容量已经达到上限,必须先删除最近最少被访问的数据,那么位于哈希链表最左端的用户1就会被删除,然后再把用户6插入最右端的位置。

    代码实现

    public class LRUCache {
        private Node head;
    
        private Node end;
    
        // 缓存存储上限
        private int limit;
    
        private HashMap<String, Node> hashMap;
    
        public LRUCache(int limit) {
            this.limit = limit;
            hashMap = new HashMap<String, Node>();
        }
    
        public String get(String key) {
            Node node = hashMap.get(key);
            if(node == null) {
                return null;
            }
    
            refreshNode(node);
            return node.value;
        }
    
        public void put(String key, String value) {
            Node node = hashMap.get(key);
            if(node == null) {
                // 如果key不存在,插入key-value
                if(hashMap.size() >= limit) {
                    String oldKey = removeNode(head);
                    hashMap.remove(oldKey);
                }
    
                node = new Node(key, value);
                addNode(node);
                hashMap.put(key, node);
            } else {
                // 如果key存在,刷新key-value
                node.value = value;
                refreshNode(node);
            }
        }
    
        public void remove(String key) {
            Node node = hashMap.get(key);
            removeNode(node);
            hashMap.remove(key);
        }
    
        /**
         * 
         * 刷新被访问的节点位置
         * 
         * @param node 被访问的节点
         * 
         */
        private void refreshNode(Node node) {
            // 如果访问的是尾节点,无需移动节点
            if(node == end) {
                return;
            }
            // 移除节点
            removeNode(node);
            // 重新插入节点
            addNode(node);
        }
    
        /**
         * 
         * 删除节点
         * 
         * @param node 要删除的节点
         * 
         */
        private String removeNode(Node node) {
            if(node == end) {
                // 移除尾节点
                end = end.pre;
            }else if(node == head) {
                // 移除头节点
                head = head.next;
            }else {
                // 移除中间节点
                node.pre.next = node.next;
                node.next.pre = node.pre;
            }
    
            return node.key;
        }
    
        /**
         * 
         * 尾部插入节点
         * 
         * @param node 要插入的节点
         * 
         */
        private void addNode(Node node) {
            if (end != null) {
                end.next = node;
                node.pre = end;
                node.next = null;
            }
    
            end = node;
    
            if (head == null) {
                head = node;
            }
        }
    
        class Node {
            Node(String key, String value) {
                this.key = key;
                this.value = value;
            }
            public Node pre;
            public Node next;
            public String key;
            public String value;
        }
    
        public static void main(String[] args) {
            LRUCache lruCache = new LRUCache(5);
            lruCache.put("001", "用户1信息");
            lruCache.put("002", "用户1信息");
            lruCache.put("003", "用户1信息");
            lruCache.put("004", "用户1信息");
            lruCache.put("005", "用户1信息");
            lruCache.get("002");
            lruCache.put("004", "用户2信息更新");
            lruCache.put("006", "用户6信息");
            System.out.println(lruCache.get("001"));
            System.out.println(lruCache.get("006"));
        }
    }
  • 相关阅读:
    [001]
    SpringBoot默认首页跳转设置
    Tomcat网站根目录设置
    SpringBoot获取前端传递JSON的几种方法
    MySQL调优性能监控之show profile
    MySQL新特性MTS
    Java线程池拒绝策略
    快速排序与荷兰国旗及Partition问题
    数据结构与算法之返回最小和问题
    MySQL之谓词下推
  • 原文地址:https://www.cnblogs.com/alimayun/p/12794650.html
Copyright © 2011-2022 走看看