面试高频——手写LRU缓存

在这里插入图片描述

思路图片先理解
在这里插入图片描述

  • 说明
    • head的next节点就是链表的最后一个(我们把链表最右边当成第一个),它就是最近最久未使用的数据

    • tail的prev节点就是链表的第一个,它代表最新最火热的数据

    • 每次get一个元素,就把它放到链表第一个去,这张图如果get1,那么相当于1放到3前面去,链表从左到右就是2,3,1,了,1成为了最新最火热的数据,如下图
      在这里插入图片描述

    • 每次put一个元素的时候,需要检查容量是否还够,如果不够就需要删除head的next节点,因为它是最近最久未使用的数据

注意
下面代码结合注释看比较容易看懂,还有就是,其实所谓的移动,只是改变指针域,我这样画只是为了方便看,不然线满天飞,很乱

class LRUCache {

    //缓存数据结构,相当于
    private class CacheNode {

        //设置为双向链表,为了方便插入和删除快速定位到前驱
        CacheNode prev;
        CacheNode next;
        int key;
        int value;
        public CacheNode(int key, int value) {
            this.key = key;
            this.value = value;
            this.prev = null;
            this.next = null;
        }
    }

    //容量
    private int capacity;

    //实际上是HashMap来存储CacheNode的,这样能迅速找到想要查找的
    private Map<Integer, CacheNode> valNodeMap = new HashMap();

    //这两个指针是为了快速更新节点到最前面(tail.prev)(最新使用的),和快速删除最后一个节点(head.next)(最近最少使用的)
    private CacheNode head = new CacheNode(-1, -1);
    private CacheNode tail = new CacheNode(-1, -1);

    public LRUCache(int capacity) {
        this.capacity = capacity;

        //一来先让它们两个互指
        tail.prev = head;
        head.next = tail;
    }
    
    public int get(int key) {
        //没有这个数据直接返回-1
        if (!valNodeMap.containsKey(key)) {
            return -1;
        }

        CacheNode current = valNodeMap.get(key);
        //get一个后,那个数据是最火热的,应该到最前面去即成为tail的前驱结点

        //因为是双向链表,所以移动之前需要先处理它前驱结点和后继节点的指针关系
        // 它前面节点的next域应该指向当前节点的next节点
        current.prev.next = current.next;

        // 它后面节点的prev域应该指向当前节点的前一个节点(即prev)
        current.next.prev = current.prev;

        //调用方法,真正移动current节点(即它成为最新最火热的节点)
        moveToTail(current);

        return current.value;
    }
    
    public void put(int key, int value) {
        if (get(key) != -1) {
            //条件满足,说明本来就有这个key,只需get方法,把它放到最前面去,并且修改它的value为新的值
            valNodeMap.get(key).value = value;
            return;
        }

        //如果容量满了,则需要删除最近最少使用的缓存节点,即head节点的next节点,因为它在最后面
        if (valNodeMap.size() == capacity) {
//请注意这里,HashMap删除元素,只是从HashMap中移除,并不是真正把该对象从内存删除
//所以这里head开头的链表并未断开,仍然可以拿到next节点
            valNodeMap.remove(head.next.key);
            head.next = head.next.next;
            head.next.prev = head;
            
            //这种写法可能好一点点
//            CacheNode record=head.next;
//            head.next=head.next.next;
//            head.next.prev=head;
//            valNodeMap.remove(record.key);
        }
    
        CacheNode insert = new CacheNode(key, value);
        //往HashMap里面插入
        valNodeMap.put(key, insert);
        //移到最前面去
        moveToTail(insert);
    }


    private void moveToTail(CacheNode current) {
        //画个图比较好理解,就是插入到tail的前驱节点的后面
        current.prev = tail.prev;
        tail.prev = current;
        current.prev.next = current;
        current.next = tail;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值