简单LRU的实现(基于LinkedHashMap)

主播今天刷leetcode刷到146.LRU缓存,并对LinkedHashMap的源码展开了学习,给大家分享一下:

先简单介绍一下题目要求,所谓LRU就是一个简单的缓存,比如说存入了[1,1],[2,2],然后我根据键值获取对应的值并对其加1,获取了[2,2]那么就把[2,2]放到容器尾部,并且将值加1,容器内部由[1,1],[2,2]变为[2,3],[1,1],并且如果LRU是有大小的,如果超出大小在添加元素后时需要舍弃最不常使用的元素的,具体的要求大家可以去看leetcode官网上面。

容器最重要的两个性能就是存入和取出的时间复杂度,我们想把这个时间复杂度压缩到O(1),因此我们对于数据结构的选择是哈希表和双向链表,每次添加新元素就会把他放在队尾,老元素被访问也会被移动到队尾,哈希表保证取数据的速度,双向链表则是方便我们对数据进行移动操作,同时在添加新元素的时候时间复杂度也是O(1)因为是在仅队尾一侧进行添加的。

选择完数据结构,再给大家介绍一下Java为我们提供的集合类LinkedHashMap,他的底层数据结构是链表、红黑树加数组,就是HashMap的基础上按照一定规则维护了一个有序的链表,这个链表的顺序有两种,一种是根据我们插入元素的顺序,而另一种则是根据我们访问的顺序,我们可以通过在构造方法中通过boolean值进行选择,false是根据插入顺序,true是根据访问顺序。和我们的需求简直天作地设。

对于LinkedHashMap,对其进行数据的插入和删除时,会通过三个回调函数来进行链表的维护:

void afterNodeAccess(Node<K,V> p) :在开启根据访问顺序调整链表后,这个方法会在访问数据后,将被访问的数据移动到链表尾部;

void afterNodeRemoval(Node<K,V> p):其作用就是在删除元素之后,将元素从双向链表中删除,还是非常建议大家去看看这个函数的,很优美的方式在双向链表中删除节点;

void afterNodeInsertion(boolean evict) :在插入元素后,用回调函数判断是否需要删除一些元素(因为对于容器的大小有限制);

对于LinkedHashMap,它有两种构造函数:

java 复制代码
/**
 * //调用父类HashMap的构造方法。
 * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
 * with the default initial capacity (16) and load factor (0.75).
 */
public LinkedHashMap() {
    super();
    accessOrder = false;
}
// 这里的 accessOrder 默认是为false,如果要按读取顺序排序需要将其设为 true
// initialCapacity 代表 map 的 容量,loadFactor 代表加载因子 (默认即可)
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

一个默认的就是调用HashMap的构造方法同时调整为根据插入顺序维护链表,另一个是自己对大小,负载因子,链表维护模式。

对于LinkedHashMap中的关键方法afterNodeInsertion是这样的:

java 复制代码
// 在插入一个新元素之后,如果是按插入顺序排序,即调用newNode()中的linkNodeLast()完成
// 如果是按照读取顺序排序,即调用afterNodeAccess()完成
// 那么这个方法是干嘛的呢,这个就是著名的 LRU 算法啦
// 在插入完成之后,需要回调函数判断是否需要移除某些元素!
// LinkedHashMap 函数部分源码

/**
 * 插入新节点才会触发该方法,因为只有插入新节点才需要内存
 * 根据 HashMap 的 putVal 方法, evict 一直是 true
 * removeEldestEntry 方法表示移除规则, 在 LinkedHashMap 里一直返回 false
 * 所以在 LinkedHashMap 里这个方法相当于什么都不做
 */
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    // 根据条件判断是否移除最近最少被访问的节点
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

// 移除最近最少被访问条件之一,通过覆盖此方法可实现不同策略的缓存
// LinkedHashMap是默认返回false的,我们可以继承LinkedHashMap然后复写该方法即可
// 例如 LeetCode 第 146 题就是采用该种方法,直接 return size() > capacity;
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

在LinkedHashMap中的removeEldestEntry是默认返回false,也就是说它默认情况不会删除元素,如果我们想要自定义删除元素的规则那我们需要重写它就好了;

至此大致思路有了,选择用LinkedHashMap+调整链表维护策略+重写removeEldestEntry就好了

题目答案代码附上

java 复制代码
class LRUCache extends LinkedHashMap<Integer,Integer>{
    private int capacity;

    public LRUCache(int capacity) {
        super(capacity,0.75F,true);
        this.capacity=capacity;
    }
    
    public int get(int key) {
        return super.getOrDefault(key,-1);
    }
    
    public void put(int key, int value) {
        super.put(key,value);       
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<Integer,Integer> eldest){
        return size() > capacity;
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */
相关推荐
叁散6 小时前
ESP32 LCD1602显示实验报告
算法
过期动态6 小时前
【LeetCode 热题 100】盛最多水的容器
java·数据结构·spring boot·算法·leetcode·spring cloud·职场和发展
凌波粒6 小时前
LeetCode--700.二叉搜索树中的搜索(二叉树)
算法·leetcode·职场和发展
君为先-bey6 小时前
LeMiCa——基于扩散模型的高效视频生成的词典序最小化路径缓存
python·算法·机器学习·扩散模型
洛水水6 小时前
【力扣100题】58.轮转数组
算法·leetcode
资深流水灯工程师6 小时前
LMS 最小均方算法在 DSP 上的 C 语言实现
算法
风筝在晴天搁浅6 小时前
阿里 LeetCode 876.链表的中间节点
算法·leetcode·链表
玖釉-7 小时前
二叉树展开为链表:从先序遍历到原地指针重排
c++·windows·算法·leetcode·链表
05候补工程师7 小时前
【408考研·数据结构专题】二叉树、树与森林、线索树及哈夫曼树核心考点与秒杀技巧深度总结
数据结构·经验分享·笔记·考研·算法
吃好睡好便好7 小时前
矩阵的加减运算
开发语言·人工智能·学习·线性代数·算法·matlab·矩阵