简单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);
 */
相关推荐
会编程的土豆1 小时前
【数据结构与算法】空间复杂度从入门到面试:不仅会算,还要会解释
数据结构·c++·算法·面试·职场和发展
普通网友1 小时前
《算法面试必刷:15 个高频 LeetCode 题(附代码)》
算法·leetcode·面试
_深海凉_1 小时前
LeetCode热题100-搜索二维矩阵
算法·leetcode·矩阵
张槊哲2 小时前
C++ 进阶指南:如何丝滑地理解与实践多线程与多进程
开发语言·c++·算法
代码中介商2 小时前
C语言链表完全指南:从单节点到链表管理
c语言·算法·链表
小小de风呀3 小时前
de风——【从零开始学C++】(四):类和对象(下)
开发语言·c++·算法
aqiu1111113 小时前
[特殊字符]【算法日记 14】数论入门神题:最大公约数与最小公倍数的“乘积守恒定律”
算法
保卫大狮兄3 小时前
一文讲清:仓库管理最核心的10个公式
人工智能·算法·仓库管理
翻身的咸鱼ing4 小时前
常用代码知识
算法·深度优先·哈希算法