【leetcode】最近最少使用缓存

0、参考资料

LRU详解:

LinkedListMap详解

一、题目描述

实现 LRUCache 类:

  • LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
  • void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

输入
[“LRUCache”, “put”, “put”, “get”, “put”, “get”, “put”, “get”, “get”, “get”]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]
解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1); // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1); // 返回 -1 (未找到)
lRUCache.get(3); // 返回 3
lRUCache.get(4); // 返回 4

二、代码思路
  • 使用栈来解决问题
    使用栈,会存在大量的内存复制,以及找元素产生的开销;
    在这里插入图片描述

首先、先访问的元素放到栈顶,栈底永远放的是最近最少被访问的元素。

其次、如果栈中存在要访问的元素,还要从栈中,取出来该元素,并放到栈底部,其余元素依次重新排列。

最后,如果我们要增加元素,也是首先把栈底元素删掉,然后将新加的元素放到栈顶部。

  • 使用LinkedListMap数据结构来解决问题
    在这里插入图片描述
    LinkedHashMap 是hash表 + 双向链表

1、我们可以通过hash值,很快找到我们要访问的缓存。

2、我们在删除缓存的时候,很容易通过hash key 找到该缓存所在位置,比如 1 ,然后我们通过链表的指针,能够不用移动内存,删掉1;同时,我们也可以通过tail指针,很容易找到,链表的尾部。

3、我们在访问缓存的时候,可以很快找到缓存,然后我们先把该位置缓存通过指针操作删掉,然后通过tail指针找到链尾,然后把最近新访问的缓存,加到链尾部分。其实LinkedHashMap 底层代码也是这么写的:

其中,after 和 before 指针是指Node节点 extends Entry 的两个指针域,也就是prev和next指针,共同维护双向链表。

https://www.cnblogs.com/xiaoxi/p/6170590.html

/**
 * 通过key获取value,与HashMap的区别是:当LinkedHashMap按访问顺序排序的时候,会将访问的当前节点移到链表尾部(头结点的前一个节点)
 */
public V get(Object key) {
    // 调用父类HashMap的getEntry()方法,取得要查找的元素。  
    Entry<K,V> e = (Entry<K,V>)getEntry(key);
    if (e == null)
        return null;
    // 记录访问顺序。
    e.recordAccess(this);
    return e.value;
}

/**
 * 在HashMap的put和get方法中,会调用该方法,在HashMap中该方法为空
 * 在LinkedHashMap中,当按访问顺序排序时,该方法会将当前节点插入到链表尾部(头结点的前一个节点),否则不做任何事
 */
void recordAccess(HashMap<K,V> m) {
    LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
    //当LinkedHashMap按访问排序时
    if (lm.accessOrder) {
        lm.modCount++;
        //移除当前节点
        remove();
        //将当前节点插入到头结点前面
        addBefore(lm.header);
    }
}
/**
 * 移除节点,并修改前后引用
 */
private void remove() {
    before.after = after;
    after.before = before;
}

private void addBefore(Entry<K,V> existingEntry) {
    after  = existingEntry;
    before = existingEntry.before;
    before.after = this;
    after.before = this;
}

4、我们也有head指针,所以能够很快找到头节点,也能很快删除头节点,头部放的元素都是最近没被访问过的元素。

5、LinkedHashMap底层都给我们封装好了

  • 1、可以通过boolean accessOrder,判断访问顺序,是按照插入顺序访问,还是按照LRU缓存的顺序访问。(也就是最新访问放到链尾,没访问的放到链头)

总结一下:

  • LinkedHashMap是由HashMap + 双向链表组成的。

  • LHM插入元素可以选择按顺序插入,按照访问顺序插入(LRU算法的实现)。

  • LHM访问元素的时候会将tail指针变更为最新访问的元素。实现方法在LHM中的afterNodeAccess

    • 代码实现就是修改访问元素前后指针,然后把tail指针指向该节点。也就是将原来节点移动到tail。
  • LHM删除元素的时候会把指定元素删除,并通过afterNodeRemoval方法来维护LRU算法。

    • 首先,维护双链表,把双链表指针修改掉。维护双链表,也就是删除该节点。
    • 其次,如果修改的是tail 或 head指针,那么也要重新给头尾指针赋值。维护LRU。
  • LHM中大多方法都是用的父类HM,但是最重要的维护双链表和LRU的算法是它自己实现的,通过多态调用。也就是上述的after等方法。

三、代码题解
package leetcode;

import java.rmi.server.SkeletonMismatchException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

/*
 * @author lzy
 * @version 1.0
 * */
public class LRU_ {
    public static void main(String[] args) {
        LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap(4, 1, true);
        //初始化缓存
        linkedHashMap.put("1", "1");
        linkedHashMap.put("2", "2");
        linkedHashMap.put("3", "3");
        linkedHashMap.put("4", "4");
        //访问缓存
        linkedHashMap.get("1");
        //加入新的缓存,会把最近没被访问的缓存删掉,如果容量不够的话
        linkedHashMap.put("5", "5");
        linkedHashMap.get("2");
        //集合不能按索引读元素,List集合才可以。
        //如何获取LHM的头节点
        linkedHashMap.remove(linkedHashMap.keySet().iterator().next());
        loopLinkedHashMap(linkedHashMap);
        /*测试LRU缓存的LinkedHashMap实现*/
        LRU_ lru_ = new LRU_(2);
        lru_.put(1, 1);
        lru_.put(2, 2);
        lru_.get(1);
        lru_.put(3, 3);
    }

    public static void loopLinkedHashMap(LinkedHashMap<String, String> linkedHashMap) {
        Set<Map.Entry<String, String>> entries = linkedHashMap.entrySet();
        Iterator<Map.Entry<String, String>> iterator = entries.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next().getKey() + " ");
        }
        return;
    }

    LinkedHashMap<Integer, Integer> linkedHashMap;
    int capacity;

    public LRU_(int capacity) {
        this.capacity = capacity;
        this.linkedHashMap = new LinkedHashMap<>(capacity, 1, true);
    }

    public int get(int key) {
        return linkedHashMap.get(key);
    }

    public void put(int key, int value) {


        if (linkedHashMap.get(key) != null) {
            linkedHashMap.put(key, value);
            return;
        }
        if (linkedHashMap.keySet().size() == capacity) {
            //这里没有考虑到这种情况:
            //put 1,5 和 put 1,2;如果key已存在则更换其值
            //那么我们考虑一下: 容量为2,先加入2,6 再加入1,5 ,再加入1,2,此时2,6就被删掉了,1,5被替换
            //这显然是不对的
            linkedHashMap.remove(linkedHashMap.keySet().iterator().next());
            linkedHashMap.put(key, value);
        } else {
            linkedHashMap.put(key, value);
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值