首页 > 编程语言 >LRU算法的应用

LRU算法的应用

时间:2025-01-18 23:10:52浏览次数:1  
标签:node Node end 应用 算法 LRU key 节点 String

13.LRU算法的应用

题目

关于用户信息的需求

假定在一个复杂的系统中,需要抽象出一个用户系统,提供给其他子系统使用,该如何实现。子系统对用户信息的查询频率很高,要注意性能问题。

用户信息是存储在数据库里的,但是对于查询频率高的数据,不能每一次请求时都去查询数据库。

思路

哈希表

使用以用户id为key,用户信息为value的哈希表,作为缓存以提高性能。存在问题,如果只向哈希表中存储数据,而不移除数据的话,可能内存会溢出,导致服务器宕机。

LRU全称Least Recently Used,最近最少未使用,是一种内存管理算法,该算法最早应用于LinuxOS。

该算法,基于一种假设:长期不使用的数据,在未来使用的概率也不大。因此,当内存数据占内存达到一定阈值时,要移除最近最少使用的数据。

该算法,还基于一种,特殊的数据结构,哈希链表。

哈希表是由若干个K-V键值对的Node节点组成的。在此逻辑基础上,每个节点,加一个pre前驱和一个next后驱,将这些Node连接起来,想链表一样。

假设,使用哈希链表存储用户信息,001-User1<->002-User2<->003-User3<->004-User4。每次,对于未缓存的用户数据,都使用尾加法,加到链表的右边,对于使用到的链表中的数据,也将该节点移动到链表的末尾。这样链表的左侧,就是最近最少未使用的数据,移除就好。

代码

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<>();
    }

    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) {
            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);
        refreshNode(node);
        hashMap.remove(key);
    }

    /**
     * 刷新被访问节点的位置
     *
     * @param node 被访问节点
     */
    private void refreshNode(Node node) {
        if (node == end)
            return;
        //移除节点
        removeNode(node);
        //重新插入节点
        addNode(node);
    }

    /**
     * 删除节点
     *
     * @param node 要删除的节点
     * @return
     */
    private String removeNode(Node node) {
        if (node == head && node == end) {
            //移除唯一节点
            head = null;
            end = null;
        } else if (node == end) {
            //移除尾结点
            end = end.pre;
            end.next = null;
        } else if (node == head) {
            //移除头节点
            head = head.next;
            head.pre = null;
        } else {
            //移除中间节点
            node.pre.next = node.next;//将当前节点的下一个,赋值给上一节点的下一个
            node.next.pre = node.pre;//将当前节点的前一个,赋值给上一节点的前一个
        }
        return node.key;
    }

    private void addNode(Node node) {
        if (end != null) {
            end.next = node;
            node.pre = end;
            node.next = null;
        }
        end = node;
        if (head == null)
            head = node;
    }

    private static 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","用户2信息");
        lruCache.put("003","用户3信息");
        lruCache.put("004","用户4信息");
        lruCache.put("005","用户5信息");
        lruCache.get("002");
        lruCache.put("004","new用户4信息");
        lruCache.put("006","用户6信息");
        System.out.println(lruCache.get("001"));
        System.out.println(lruCache.get("006"));
    }

}

注意,该代码本质还是使用HashMap作为缓存,且如果多个线程共享一个LRUCache对象时,访问这些方法,是线程不安全的。这是可以给这些方法手动加锁,或者使用ConcurrentHashMap类占存数据。本demo中的,存储的用户使用String类型表示,只是为了演示LRU算法,实际使用中,可以使用泛型

对于这种缓存高频访问的数据,一般都是用Redis这种NoSQL的内存数据库,其底层也实现了类似LRU的回收算法。该笔记只是展示LRU算法。而且使用缓存数据库,就一定存在一个数据一致性的问题,就是关系型数据库中存储的数据和缓存中的数据如何保持一致?后续有机会分享。

只是为了记录自己的学习历程,且本人水平有限,不对之处,请指正。

标签:node,Node,end,应用,算法,LRU,key,节点,String
From: https://www.cnblogs.com/changming06/p/18679009

相关文章

  • 最大流问题:增广路与 Edmonds-Karp 算法
    最大流问题是其中一个经典的图论问题,其目标是在一个流网络中计算从源点到汇点的最大流量。流网络由节点和边组成,每条边都有一个容量,表示该边所能承载的最大流量。最大流问题通常来说,最大流问题仅在有向图上考虑,允许成环,且不考虑重边和自环。在数学上,流网络可以表示为一个有向图......
  • 运算放大器应用电路设计笔记(四)
    动态范围表示正常工作时最小振幅与最大振幅的范围。例如,最小振幅为-14v,最大振幅为+14v,则动态范围为±14v,也有用绝对值或有效值表示振幅,最大电压与最小电压之比为动态范围,也称为多少dB。这时,最大振幅由电源电压决定,最小振幅由噪声或失调电压决定。确保动态范围的最简单方法......
  • 保姆级解析雪花算法原理,看完必懂!
    引言最近发现项目里主键id生成算法很短小精悍,遂深入看了下,还蛮有意思,在此分享一下,源码如下。privatestaticSpinLockmLock=newSpinLock();privatestaticvolatileintrotateId=0;privatestaticvolatilelongtimeId=0;privatestaticintnodeI......
  • 机器学习算法深度解析与实践案例:以随机森林为例
    机器学习算法深度解析与实践案例:以随机森林为例在当今大数据驱动的时代,机器学习作为人工智能的一个核心分支,正以前所未有的速度改变着各行各业。从金融风控到医疗健康,从自动驾驶到智能推荐系统,机器学习算法的应用无处不在。本文将深入探讨一种广泛应用于分类和回归任务的强......
  • 七大排序算法
    文章目录排序的概念及引用1.插入排序2.希尔排序(缩小增量排序)3.选择排序4.堆排序5.冒泡排序6.快速排序7.归并排序8.代码排序部分的测试9.代码加效果大致测试时间(仅供参考)排序的概念及引用排序:将数据按照特定的规律排成递增或递减的操作稳定性:例如arr数组中arr[i......
  • exgcd(扩展欧几里得算法)
    当我们要求解ax+by=c时,注意到x和y应该是一个解集,c是a的x倍加上b的y倍的和,假设gcd(a,b)==d,那么,c也应该是d的整数倍,即d|c.那么根据这,我们可以想到在思考ax+by=c的解集前,我们可以先思考ax+by=d的解集,注意到等式右边缩小了c/d倍,假设原解集为x1,y1,现解集为x2,y2,那么将x2,y2扩大c/......
  • 极坐标与直角坐标之间变换的原理和应用示例
            极坐标变换的原理是将平面上的点从直角坐标系转换为极坐标系,或者从极坐标系转换为直角坐标系。以下是关于极坐标变换原理的详细解释:一、极坐标系的基本概念        在极坐标系中,一个点的位置由两个参数确定:径向距离(ρ)和极角(θ)。        (1)......
  • 数据结构与算法之栈: LeetCode 71. 简化路径 (Ts版)
    简化路径https://leetcode.cn/problems/simplify-path/description/描述给你一个字符串path,表示指向某一文件或目录的Unix风格绝对路径(以‘/’开头),请你将其转化为更加简洁的规范路径在Unix风格的文件系统中规则如下一个点‘.’表示当前目录本身此外,两个......
  • 在 nuget 私服 BaGet 中应用https 以及 gitea action runner 自动打包
    最近赋闲,想起之前工作中代码管理的一点经历,就是在打包项目的时候,类库的版本号几乎没变过,一个项目运行多少年了,版本号还是1.0.0。......
  • 第二天算法设计
    选择排序需求:排序前:{4,6,8,7,9,2,10,1}排序后:{1,2,4,5,7,8,9,10}算法设计:Selection类:packagesuanfa;publicclassSelection{//对数组a中的元素进行排序publicstaticvoidsort(Comparable[]a){for(inti=0;i<a.length-1;i++){intminIdex=i;for(intj=i+1;j<a.length;j++......