- LRU 缓存 - 力扣(LeetCode)
LRU算法设计
cache 这个数据结构必要的条件:
- cache 中的元素必须有时序,以区分最近使用和久未使用的数据。当容量满了之后删除最久未使用的元素
- 如果我们每次默认从链表尾部添加元素,那么显然越靠尾部的元素就是最近使用的,越靠头部的元素就是最久未使用的。
- 需要在cache中快速找某个key是否已经存在并得到对应的val
- 对于某一个 key,我们可以通过哈希表快速定位到链表中的节点,从而取得对应 val
- 每次访问cache 中的某个key,需要将这个元素变为最近使用的,也就是说 cache 要支持在任意位置快速插入和删除元素。
- 链表显然是支持在任意位置快速插入和删除的,改改指针就行。只不过传统的链表无法按照索引快速访问某一个位置的元素,而这里借助哈希表,可以通过 key 快速映射到任意一个链表节点,然后进行插入和删除。
LRU 缓存算法的核心数据结构就是哈希链表,双向链表和哈希表的结合体。
代码实现
LinkedHashMap,它与HashMap的区别就是在Map的结构外还有一个链表结构,Map的每个key-value都会以Node的形式同时存储在Map和链表中
当put一个key和value时,加入Map的同时,也加入链表尾部,这样实现了有序(HashMap本来是无序的)
正是这个链表完成了LRU的实现。
- get
(使用了)的时候将该Node从链表中移除,并添加到链表尾部。 - put
检查Map的容量,如果超出了,就从移除链表头部Node(时间最久的,即最近最少使用),同时从map中移除。
使用JAVA实现
// 使用LinkedHashMap实现LRU算法
class LRUCache {
int cap;
LinkedHashMap<Integer, Integer> cache = new LinkedHashMap<>();
public LRUCache(int capacity) {
this.cap = capacity;
}
public int get(int key) {
if(!cache.containsKey(key)){
return -1;
}
// 否则,将key变为最近使用
makRecently(key);
return cache.get(key);
}
public void put(int key, int value) {
// 如果key已经存在
if(cache.containsKey(key)){
// 修改key的值
cache.put(key, value);
makRecently(key);
return;
}
// 如果不存在,判断容量满没满,如果已满
if(cache.size() >= this.cap){
// 链表头部就是最久未使用的key,删除链表头
int oldestKey = cache.keySet().iterator().next();
cache.remove(oldestKey);
}
// 将新的key添加到链表尾部
cache.put(key, value);
}
private void makRecently(int key){
int val = cache.get(key);
// 删除key,重新插入到队尾
cache.remove(key);
cache.put(key, val);
}
}
/**
* 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);
*/