今天看了一道比较具有实用价值的题目,即如何实现LRU缓存
题目描述如下:

题目分析
这道题的话实际上是要设计一种数据结构,并且满足以下条件:
1、显然 cache
中的元素必须有时序,以区分最近使用的和久未使用的数据,当容量满了之后要删除最久未使用的那个元素腾位置。
2、我们要在 cache
中快速找某个 key
是否已存在并得到对应的 val
;(联想到HashMap)
3、每次访问 cache
中的某个 key
,需要将这个元素变为最近使用的,也就是说 cache
要支持在任意位置快速插入和删除元素。(联想到双向链表)
哈希表查找快,但是数据无固定顺序;链表有顺序之分,插入删除快,但是查找慢。所以结合一下,形成一种新的数据结构:哈希链表 LinkedHashMap
。
自己造轮子写LinkedHashMap
如果要自己实现LinkedHashMap,需要写一个双向链表和哈希表组合起来使用,如下面代码所示:
// 自己造轮子版实现LRU
class Node{
public int key, val;
public Node prev, next;
public Node(int k, int v){
this.key = k;
this.val = v;
}
}
class doubleList{
// 头节点和尾节点
private Node head, tail;
// 链表长度
private int size;
// 初始化双向链表
public doubleList(){
head = new Node(0, 0);
tail = new Node(0, 0);
head.next = tail;
tail.prev = head;
size = 0;
}
// 向链表尾部添加节点
public void addLast(Node x){
x.prev = tail.prev;
x.next = tail;
tail.prev.next = x;
tail.prev = x;
size++;
}
// 删除链表中的x节点
public void remove(Node x){
x.prev.next = x.next;
x.next.prev = x.prev;
size--;
}
// 删除链表中第一个节点并返回该节点
public Node removeFirst(){
if(size==0) return null;
Node first = head.next;
remove(first);
return first;
}
// 返回双链表的长度
public int size(){
return size;
}
}
class LRUCache {
// 定义一个HashMap,主要实现O(1)查找
private HashMap<Integer, Node> map;
// 定义一个双向链表来实现LRU算法
private doubleList cache;
// 最大容量
private int cap;
// 将某个key提升为最近使用过的
private void makeRecently(int key){
Node x = map.get(key);
// 先在链表中删除这个节点
cache.remove(x);
// 然后在链表尾部添加这个节点
cache.addLast(x);
}
// 添加最近使用的元素
private void addRecently(int key, int value){
Node x = new Node(key, value);
// 加到链表尾部,表示最近使用过的节点
cache.addLast(x);
// 在map中添加映射
map.put(key, x);
}
// 删除某一个 key
private void deleteKey(int key) {
Node x = map.get(key);
// 从链表中删除
cache.remove(x);
// 从 map 中删除
map.remove(key);
}
// 删除最久未使用的元素
private void removeLeastRecently() {
// 链表头部的第一个元素就是最久未使用的
Node deletedNode = cache.removeFirst();
// 同时从 map 中删除它的 key
int deletedKey = deletedNode.key;
map.remove(deletedKey);
}
public LRUCache(int capacity) {
this.cap = capacity;
map = new HashMap<>();
cache = new doubleList();
}
public int get(int key) {
if(!map.containsKey(key)){
return -1;
}
makeRecently(key);
return map.get(key).val;
}
public void put(int key, int value) {
if (map.containsKey(key)) {
// 删除旧的数据
deleteKey(key);
// 新插入的数据为最近使用的数据
addRecently(key, value);
return;
}
if (cap == cache.size()) {
// 删除最久未使用的元素
removeLeastRecently();
}
// 添加为最近使用的元素
addRecently(key, value);
}
}
java提供的LinkedHashMap题解
class LRUCache {
// 容量
int cap;
// 缓存,采用LinkedHashMap实现
LinkedHashMap<Integer, Integer> cache;
// 把一个key变成最近使用
private void makeRecently(int key){
int val = cache.get(key);
// 先删掉在加到cache里面
cache.remove(key);
cache.put(key, val);
}
public LRUCache(int capacity) {
this.cap = capacity;
this.cache = new LinkedHashMap<>();
}
public int get(int key) {
if(!cache.containsKey(key)){
return -1;
}
makeRecently(key);
return cache.get(key);
}
public void put(int key, int value) {
if(cache.containsKey(key)){
makeRecently(key);
cache.put(key, value);
return;
}
if(cache.size()>=cap){
// 链表头部就是最久未使用的 key
int lastKey = cache.keySet().iterator().next();
cache.remove(lastKey);
}
// 将新的 key 添加链表尾部
cache.put(key, value);
}
}