每日一题:写一个LRU缓存算法

878 阅读2分钟

每日一题:写一个LRU缓存算法

题目

请实现一个LRU缓存处理逻辑?

分析

  • 什么是LRU缓存

Least Recently Used 最近最少使用;

核心思想:"如果数据最近被访问过,那么将来被访问的几率也更高"

也就说、在空间不足的情况下、或者说限定存储空间大小的时候,我们要删掉最近使用频率最低的的数据。

  • 思考步骤
  1. 如何存储数据:Object,Array,Set,Map

  2. 如何表现数据的使用频率

    1. 手动排序,每次读取数据时候,把当前数据放到最后
    2. 利用JS中的Map,自动实现排序(Map的key是有序的,而且符合FIFO原则,利用这一特性,我们可以和方便的实现)

提示

JS中的Map的特点:

  1. key可是任何类型的数据
  1. key有是有序,FIFO原则
  1. 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的时候怎么来实现呢?

根据上面的解释,我们要实现俩种逻辑

  1. 数据的有序存储
  2. 数据的快速读取,移动位置

大家想到用什么数据结构了吗?

双向链表+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
    }
}
​

\