每日一题:写一个LRU缓存算法
题目
请实现一个LRU缓存处理逻辑?
分析
- 什么是LRU缓存
Least Recently Used 最近最少使用;
核心思想:"如果数据最近被访问过,那么将来被访问的几率也更高"
也就说、在空间不足的情况下、或者说限定存储空间大小的时候,我们要删掉最近使用频率最低的的数据。
- 思考步骤
如何存储数据:Object,Array,Set,Map
如何表现数据的使用频率
- 手动排序,每次读取数据时候,把当前数据放到最后
- 利用JS中的Map,自动实现排序(Map的key是有序的,而且符合FIFO原则,利用这一特性,我们可以和方便的实现)
提示
JS中的Map的特点:
- key可是任何类型的数据
- key有是有序,FIFO原则
- keys()方法返回的是是一个遍历器,可以按顺序遍历key
解答
基于上面说的Map特性,实现
class LRUCache {
/**
* 实例化的时候、设置空间大小,也就是缓存数据长度
* 注意:size<=1时,意义不大
* @param {*} size
*/
constructor(size){
this.size=size;
this.cacheInstance=new Map();
if(size<=1){
throw new Error('请分配合理的缓存大小')
}
}
/**
* 像缓存中添加数据
* 根据map的FIFO原则,新写入的数据、排序在最后
* @param {*} key
* @param {*} value
*/
write(key,value){
//已存在的数据、直接删除,在添加、这可以保证数据存储在最后,
if(this.cacheInstance.has(key)){
this.cacheInstance.delete(key)
}else{
if(this.cacheInstance.size>=this.size){
var firstKey= this.cacheInstance.keys().next().value
this.cacheInstance.delete(firstKey);
}
}
this.cacheInstance.set(key,value)
}
/**
* 读取数据
* @param {*} key
* @returns
*/
read(key){
if(this.cacheInstance.has(key)){
const value=this.cacheInstance.get(key);
//读取数据的时候,我们把读取的数据、放到最后,表示数据最近使用过
this.cacheInstance.delete(key);
this.cacheInstance.set(key,value);
return value
}else{
return null;
}
}
}
进阶解答
我们一般在面试的时候、回答手写类的问题时,我们一般用es5的语法来写,更能提现一个人的专业度
那么、在不使用Map的时候怎么来实现呢?
根据上面的解释,我们要实现俩种逻辑
- 数据的有序存储
- 数据的快速读取,移动位置
大家想到用什么数据结构了吗?
双向链表+Object
代码示例:
/**
* 定义链表节点
*/
class LinkNode {
constructor(key,value){
this.key=key;
this.value=value;
this.next=null;
this.parent=null;
}
setParent(node){
this.parent=node
}
setNext(node){
this.next=node
}
setValue(value){
this.value=value
}
}
/**
* 基于假头双链表实现的LRU
*/
class LRUCache {
constructor(size){
this.size=size;
this.keyValueMap={};
this.count=0;
//初始化的时候、创建一个'假头'节点
this.headNode=new LinkNode();
this.tailNode=this.headNode;
}
write(key,value){
//如果key不存在、直接添加到链表结尾
if(!this.keyValueMap[key]){
const node=new LinkNode(key,value);
this.tailNode.setNext(node)
node.setParent(this.tailNode);
this.tailNode=node;
this.keyValueMap[key]=node;
this.count++;
}else{
//如果已存在,把数据移动到结尾,然后更新缓存值
let node=this.keyValueMap[key];
this._moveTail(node);
node.setValue(value)
}
if(this.count>this.size){
this._removeHead();
}
}
_moveTail(node){
//如果是尾节点、不做操作
if(!node.next){
return null
}
//首先,把当前节点从链表中移除,
node.parent.setNext(node.next);
node.next.setParent(node.parent);
//把当前节点放到尾节点
this.tailNode.setNext(node);
node.setParent(this.tailNode);
//把尾节点指针、移到最后
this.tailNode=node;
node.setNext(null)
}
//数据满了、则之前移除头结点、因为这里采用
_removeHead(){
delete this.keyValueMap[this.headNode.next.key]
this.headNode.next=this.headNode.next.next
this.headNode.next.parent=this.headNode;
}
read(key){
var node=this.keyValueMap[key];
if(node){
this._moveTail(node)
return node
}
return null
}
}
\