1.什么是LRU缓存?
LRU缓存是一个指定大小容量的缓存,随着元素不断的增加,如果达到容量的上线,会优先删除最近最少使用的数据。同时每次put/get操作都会调整数据位置为最新的缓存,每次put/get操作的时间复杂度是O(1)
2.LRU的应用场景有哪些?
mysql的buffer pool 缓存策略是LRU的优化版,redis 内存不足的缓存淘汰策略有LRU等
3.LRU的实现方式有哪些?
一种使用自实现双向链表+hashmap,第二种是指定大小容量的linkedhashmap,内部结构如图所示
put(k,v) 实现逻辑
分三种情况
1.检测map中是否存在,如果存在,先从链表删除,新节点添加到链表末尾,添加到hashmap中
2.map中不存在,map没有达到最大容量,添加到链表末尾,添加到hashmap中
3.map中存在.先从链表中删除头结点(头结点存放最近最少使用的节点),hashmap中移除头结点,新节点添加到链表
put(k,v) 实现逻辑
分两种情况
1.检测hashmap中是否存在,如果存在,先从链表中删除,新节点添加链表尾部,并返回value值
2.如果hashmap中不存在.直接返回-1
LRU缓存策略实现
第一种基于自实现的双向链表+hashmap的实现如下
package com.mashibing.my;
import java.util.HashMap;
import java.util.Map;
//自实现双向链表+hashmap=lru(最近最少使用缓存)
//put(k,v)
//分三种情况
//1.检测map中是否存在,如果存在,先从链表删除,新节点添加到链表末尾,添加到hashmap中
//2.map中不存在,map没有达到最大容量,添加到链表末尾,添加到hashmap中
//3.map中存在.先从链表中删除头结点(头结点存放最近最少使用的节点),hashmap中移除头结点,新节点添加到链表中,添加到map中
//get(k,v)
//1.检测hashmap中是否存在,如果存在,先从链表中删除,新节点添加链表尾部,并返回value值
//2.如果hashmap中不存在.直接返回-1
public class LRUCache2 {
class Node {
Node pre;
Node next;
Integer key;
Integer val;
public Node() {
}
Node(Integer k, Integer v) {
key = k;
val = v;
}
}
Map<Integer, Node> map = new HashMap<Integer, Node>();
//双向链表的头结点和尾结点
Node head, tail;
//最大容量
int cap;
public LRUCache2(int capacity) {
this.cap = capacity;
this.head = new Node();
this.tail = new Node();
head.next = tail;
tail.pre = head;
}
public int get(int key) {
Node n = map.get(key);
if (n != null) {
//删除n节点
deleteNode(n);
appendTail(n);
return n.val;
}
return -1;
}
public void put(int key, int value) {
Node n = map.get(key);
// existed
if (n != null) {
n.val = value;
map.put(key, n);
deleteNode(n);
appendTail(n);
return;
}
// else {
if (map.size() == cap) {
//达到链表的长度,从链表的头部删除元素
//删除头结点
Node tmp = head.next;
deleteNode(head.next);
//map中移除key
map.remove(tmp.key);
}
n = new Node(key, value);
// youngest node append tail
//向链表的尾部添加元素
appendTail(n);
map.put(key, n);
}
private void appendTail(Node n) {
Node cur = tail.pre;
n.next = tail;
n.pre = cur;
cur.next = n;
tail.pre = n;
}
private void deleteNode(Node n) {
n.pre.next = n.next;
n.next.pre = n.pre;
}
public static void main(String[] args) {
LRUCache2 cache = new LRUCache2(2 /* 缓存容量 */);
cache.put(1, 1);
cache.put(2, 2);
System.out.println(cache.get(1)); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
System.out.println(cache.get(2)); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
System.out.println(cache.get(1)); // 返回 -1 (未找到)
System.out.println(cache.get(3)); // 返回 3
System.out.println(cache.get(4)); // 返回 4
}
}
第二种实现方式 指定大小容量的linkedhashmap
import java.util.LinkedHashMap;
import java.util.Map;
public class LRUcache3 {
private int capacity;
private LinkedHashMap<Integer, Integer> cache;
public LRUcache3(int capacity) {
this.capacity = capacity;
this.cache = new java.util.LinkedHashMap<Integer, Integer> (capacity, 0.75f, true) {
// 定义put后的移除规则,大于容量就删除eldest
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size() > capacity;
}
};
}
public int get(int key) {
Integer value = cache.get(key);
if(value==null){
return -1;
}
cache.remove(key);
cache.put(key,value);
return value;
}
public void put(int key, int value) {
if (cache.containsKey(key)) {
cache.remove(key);
}
cache.put(key, value);
}
public static void main(String[] args) {
LRUcache3 cache = new LRUcache3(2 /* 缓存容量 */);
cache.put(1, 1);
cache.put(2, 2);
System.out.println(cache.get(1)); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
System.out.println(cache.get(2)); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
System.out.println(cache.get(1)); // 返回 -1 (未找到)
System.out.println(cache.get(3)); // 返回 3
System.out.println(cache.get(4)); // 返回 4
}
}