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
}
}
本文到这就结束啦,欢迎下次再来一起学习ヾ(◍°∇°◍)ノ゙!!