手写LRU缓存

#一人分享一道面试手撕题#比较经典的题目,如果之前没写过的可能需要想一番,首先实现缓存最先想到的就是hashmap,O1级别的查找速度很适合做缓存,然后就是要实现lru,参考redis的zset底层实现,zset也是使用了两个数据结构跳表+hashmap,使用跳表实现排序,我们这里也是使用双向链表实现lru功能,每次查询对应数据的时候就将数据移除重新加到头部,也就是更新使用频率,附上代码如下
比较经典的题目,如果之前没写过的可能需要想一番,首先实现缓存最先想到的就是hashmap,O1级别的查找速度很适合做缓存,然后就是要实现lru,参考redis的zset底层实现,zset也是使用了两个数据结构跳表+hashmap,使用跳表实现排序,我们这里也是使用双向链表实现lru功能,每次查询对应数据的时候就将数据移除重新加到头部,也就是更新使用频率
//通过自定义节点,hashmap,哨兵节点
//删除时通过pre和next指针快速删除节点
//添加时只操作头尾,通过哨兵节点快速添加节点
class LRUCache {
    private class Node{
        //保留key,不然在删除尾结点的时候不能返回key让map也删除
        //而map又不知道尾结点是哪个
        int key,value;
        Node pre,next;
        Node(int key,int value){
            this.key=key;
            this.value=value;
        }
        Node(int key,int value,Node pre,Node next){
            this.key=key;
            this.value=value;
            this.pre=pre;
            this.next=next;
        }
    }
    private int capacity;
    //通过哨兵节点可以快速找到头结点和尾节点
    private Node dummy;
    //通过hashmap快速找到节点,通过节点pre指针和next指针快速实现删除
    private Map<Integer,Node> map=new HashMap();

    public LRUCache(int capacity) {
        this.capacity=capacity;
        //不能dummy=new Node(-1,-1,dummy,dummy)
        //这样会导致dummy的pre和next为null
        dummy=new Node(-1,-1);
        dummy.pre=dummy;
        dummy.next=dummy;
    }
    
    public int get(int key) {
        if(!map.containsKey(key)) return -1;
        //如果存在,更新使用频率(加到头部)
        Node node=map.get(key);
        remove(node);
        addFirst(node);
        return node.value;
    }
    
    public void put(int key, int value) {
        Node node;
        if(!map.containsKey(key)){
            while(map.size()>=capacity){
                int removeKey=removeLast();
                map.remove(removeKey);
            }
            node=new Node(key,value);
            addFirst(node);
        }else{
            //否则更新数值重新加入
            node=map.get(key);
            node.value=value;
            remove(node);
            addFirst(node);
        }
        map.put(key,node);

    }
    public void remove(Node node){
        Node pre=node.pre;
        Node next=node.next;
        pre.next=next;
        next.pre=pre;
    }
    public int removeLast(){
        Node tail=dummy.pre;
        remove(tail);
        return tail.key;
    }
    public void addFirst(Node node){
        //通过哨兵结点快速找到头结点
        Node head=dummy.next;
        head.pre=node;
        node.next=head;
        dummy.next=node;
        node.pre=dummy;
    }
}
全部评论
哨兵节点妙啊
点赞 回复 分享
发布于 04-07 10:58 北京
算法题好恶心
点赞 回复 分享
发布于 01-11 17:20 陕西

相关推荐

头像
04-15 01:03
南京大学 Java
(Java&nbsp;后端开发)一、&nbsp;个人背景与经历挖掘自我介绍AI&nbsp;工具在学习实践中的融入追问:AI&nbsp;工具的具体使用方式与协同二、&nbsp;计算机基础与&nbsp;Java&nbsp;核心栈和队列的区别及适用场景:追问&nbsp;1:基于数组实现栈和队列的注意点:追问&nbsp;2:循环队列的扩容逻辑与注意事项接口(Interface)与抽象类(Abstract&nbsp;Class)的区别追问&nbsp;1:支付系统设计(支付宝、微信等),选接口还是抽象类?追问&nbsp;2:通用的日志和权限校验逻辑应该放在哪?三、&nbsp;数据库与架构设计分库分表策略及高并发应用追问&nbsp;1:某个分片出现热点问题如何解决?追问&nbsp;2:加盐打散和缓存如何避免对业务逻辑/查询性能产生负面影响?项目管理系统表结构设计(包含项目、任务、成员)追问&nbsp;1:多成员负责多任务的数据库表设计追问&nbsp;2:任务优先级和状态频繁变化,如何保证灵活性?四、&nbsp;网络通信与接口设计大模型文本生成&nbsp;HTTP&nbsp;接口设计(替换了原本的成本控制题(不会成本控制)追问&nbsp;1:流式返回(Stream)的具体数据传输设计追问&nbsp;2:错误码如何设计以便前端快速定位?五、&nbsp;场景解决与行为面试识别并解决潜在隐患的经历追问&nbsp;1:布隆过滤器的误判与分布式锁的性能瓶颈应对追问&nbsp;2:极端流量下的边界条件与降级策略
查看21道真题和解析
点赞 评论 收藏
分享
评论
点赞
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务