LRU 缓存机制
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get
和 写入数据 put
。
获取数据 get(key)
- 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value)
- 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
进阶:
你是否可以在 O(1) 时间复杂度内完成这两种操作?
示例:
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
分析
首先,为了不愿意往下看的同学,直接给出核心点:
- 令 LinkedHashMap 的 accessOrder 属性值为 true
- 重写 removeEldestEntry 方法
下面就是 吹逼 讲解 了。
最近学习 Android,看到 关于 LruCache 的实现,发现其内部实际上核心就是维护了一个 LinkedHashMap,于是乎,去查了查 API,发现了一个有意思的方法:
protected boolean removeEldestEntry(Map.Entry<K,V> eldest)
官方文档中写道:
Returns
true
if this map should remove its eldest entry. It provides the implementor with the opportunity to remove the eldest entry each time a new one is added.This is useful if the map represents a cache: it allows the map to reduce memory consumption by deleting stale entries.
这不正是我们实现 LruCache 所需要的么,”超过容量后,添加新内容时,移除’最旧’的那个”。
官方文档中也给出了使用的示例:
private static final int MAX_ENTRIES = 100;
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
//保证有 100 个 entrys,超过时,删掉最早加入的那个。
有了这个方法,我们只需要再做一些补充就大功告成了。
下面需要补充一些 LinkedHashMap 的知识。
至于详细分析,推荐一篇文章 https://blog.csdn.net/justloveyou_/article/details/71713781,分析的很清楚。
LinkedHashMap 有这样一个构造函数:
public LinkedHashMap(int initialCapacity, float loadFactor,boolean accessOrder)
这个 accessOrder 参数控制了迭代顺序,true 时表示按照访问顺序迭代,false 时表示按照插入顺序迭代。
简单来说,原本 LinkedHashMap 遍历顺序为 A->B->C,此时访问 B,再添加 D
- 当 accessOrder 为 true 时,遍历顺序变为 A->C->B->D
- 当 accessOrder 为 false 时,遍历变为 A->B->C->D
因此我们实现 LruCache 只需要再令 accessOrder = true 即可,此时最新访问/添加的永远在最尾端,结合 removeEldestEntry 方法,当到达容量时,优先删除最久未访问的内容。
代码
class LRUCache {
//看了看提交记录里最快的解法,发现可以直接让 LRUCache 类继承 LinkedHashMap
//不需要像我这里,重新定义一个类,然后再通过组合的方式使用。
//属实汗颜 = = ,
//个人看法,尽量避免对给出的类做改动
//毕竟实际运用时,别人就给一个这样的类,我们擅自改动这个类的继承关系难免会引起不必要的麻烦
MapCache cache;
public LRUCache(int capacity) {
cache = new MapCache(capacity);
}
public int get(int key) {
return cache.getOrDefault(key,-1);
}
public void put(int key, int value) {
cache.put(key,value);
}
}
class MapCache extends LinkedHashMap<Integer, Integer> {
private int MAX;
//调用父类构造函数,另 accessOrder = true
MapCache(int max){
super(max,0.75f,true);
this.MAX = max;
}
protected boolean removeEldestEntry(Map.Entry<Integer,Integer> eldest){
return size() > MAX;
}
}
/**
* 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);
*/