LRU详解:用JavaScript实现LRU缓存算法

238 阅读4分钟

LRU(Least Recently Used)是一种常用的缓存淘汰策略。LRU 算法的基本思路是,当缓存空间满时,优先淘汰最近最少使用的数据。它可以应用于什么场景当中呢?

LRU的应用

在许多应用的用户系统中心中,集中管理用户信息非常重要。然而,随着用户数量的增加和用户活动的频繁,系统面临着访问压力巨大的挑战。用户系统中心通常会涉及大量的数据库查询操作,而频繁的数据库查询操作可能会导致I/O瓶颈。为了缓解这个问题,我们可以引入缓存机制。LRU缓存算法是一种常用的缓存算法,它根据数据的最近访问时间来淘汰最少使用的数据。通过将用户信息存储在缓存中,可以避免频繁的数据库查询,从而提高系统的响应速度和吞吐量。

当用户首次访问系统时,系统可以通过查询缓存来获取用户信息,如果缓存中不存在,则从数据库中读取,并将其存储在缓存中以备后续使用。对于频繁访问的用户,其信息将会长时间保存在缓存中,从而提高查询效率。然而,缓存也是有容量上限的,因此需要合理设置缓存的大小,避免内存溢出的问题。

LRU的实现

用JavaScript实现LRU算法需要借助哈希表和双向链表。使用哈希表可以快速查找节点,而使用双向链表可以方便地进行节点的插入、删除和更新操作。当缓存空间不足时,通过删除链表尾部的节点来腾出空间,同时在哈希表中删除对应的键。这样可以保证最近访问的节点总是位于链表的头部,最久未访问的节点总是位于链表的尾部。

代码如下:

// 定义一个ListNode类作为链表节点的数据结构
class ListNode{
    constructor(key,val){
        this.key = key; 
        this.val = val;
        this.pre = null; // 前一个节点
        this.next = null; // 后一个节点
    }
}
// 定义一个LRUCache类,构造函数接收一个容量(capacity)参数
class LRUCache{
    constructor(capacity){
        this.capacity = capacity; // LRU缓存的容量
        this.size = 0; // 当前缓存中的节点数量
        this.data = {}; // 一个哈希表,用于存储缓存中的节点。可以通过key快速查找到对应的节点,时间复杂度为O(1)
        this.head = new ListNode(); // 指向链表头部的虚拟节点
        this.tail = new ListNode(); // 指向链表尾部的虚拟节点
        this.head.next = this.tail;
        this.tail.pre = this.head;

    }
    // put(key, val)方法用于向缓存中插入或更新节点
    put(key,val){
        // 如果key不存在于缓存中,就创建一个新的节点,并将其加入到哈希表和链表的头部
        if(!this.data[key]){             
            let node = new ListNode(key,val)
            this.data[key] = node;
            this.appendHead(node);
            this.size++;
            // 如果缓存已满,则删除链表尾部的节点,并从哈希表中删除对应的键
            if(this.size>this.capacity){
                const lastKey = this.removeTail()
                delete this.data[lastKey]
                this.size--
            }
        }
        // 如果key已存在于缓存中,先将对应节点从链表中移除,然后更新节点的值,并将节点重新插入到链表的头部
        else{            
            let node = this.data[key];
            this.removeNode(node);
            node.val = val;
            this.appendHead(node);
        }
    }
    // get(key)方法用于获取缓存中指定key的值
    get(key){
        // 如果key不存在于缓存中,返回-1;
        if(!this.data[key])return -1;
        // 如果存在于缓存中,则先将对应节点从链表中移除,然后将该节点插入到链表的头部,并返回节点的值
        else{
            let node = this.data[key];
            this.removeNode(node);
            this.appendHead(node);
            return node.val;
        }
    }
    // appendHead(node)方法用于将一个节点插入到链表的头部
    appendHead(node){
        // 将node插入到head节点和原来的第一个节点之间
        let firstNode = this.head.next;
        this.head.next = node;
        node.pre = this.head;
        node.next = firstNode;
    }
    // removeTail()方法用于移除链表尾部的节点,并返回被移除节点的键
    removeTail(){
        //获取尾部前一个节点的键
        let key = this.tail.pre.key;
        // 调用removeNode方法将尾部前一个节点从链表中移除
        this.removeNode(this.tail.pre)
        return key;
    }
    // removeNode(node)方法用于从链表中移除指定节点
    removeNode(){
        // 修改该节点前一个节点的next指针和后一个节点的pre指针,从而将该节点从链表中断开。
        let preNode = node.pre;
        let nextNode = node.next;
        preNode.next = nextNode;
        nextNode.pre = preNode
    }
}

本文到这就结束啦,欢迎下次再来一起学习ヾ(◍°∇°◍)ノ゙!!