一、什么是LRU
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。当然我们最熟悉的redis中的内存淘汰策略也可以使用LRU策略。
二、如何去实现
LRU的原理是在我们访问缓存中的一个数据的时候会认为当前这个数据是热点数据,而那些在缓存中长时间没有被访问的数据会认为是很肯能不再使用的,所以当缓存快满时,就会删除那些长期不使用的数据。
这里我会展示两种实现方式:
1.利用hashmap和一个双向链表实现
- 1.查询逻辑:当key在map中时,返回数据把查询的节点删除并添加到链表的头部;当key不在map中,返回null
- 2.存储逻辑:缓存中没有数据时,放入头节点;当key在map中时,替换value值并把当前节点删除添加到链表头部;当key不在map中时,判断缓存中的容量是否充足,如果
- 缓存满了,就会删除链表尾部节点,再添加对应节点到头部;如果充足,存到map中,并把节点添加到头部
//缓存数据节点
class Node{
Node preNode;
Node nextNode;
Object value;
String key;
public Node(String key,Object value){
this.key=key;
this.value=value;
}
}
public class LRUCache {
//快速定位缓存节点
Map<String,Node> map = new HashMap();
//保留头和尾节点数据
Node head;
Node tail;
//缓存容量
int capacity;
public LRUCache(int capacity){
this.capacity=capacity;
}
public Object get(String key){
if(null!=map.get(key)){//缓存里有对应的数据,更新缓存数据的位置
Node tmp = map.get(key);
removeAndInsert(tmp);
return tmp.value;
}
return null;
}
public void put(String key,Object value){
if(head==null){//缓存是空的情况
head = new Node(key,value);
tail=head;
map.put(key,head);
return;
}
Node tmp = map.get(key);
if(tmp!=null){//缓存里有对应的数据,覆盖value,并更新缓存数据的位置
tmp.value=value;
removeAndInsert(tmp);
}else{//新建数据节点并放到头节点,如果超过容量删除最后的节点
Node node = new Node(key,value);
if(capacity<=map.size()){
map.remove(tail.key);
tail = tail.preNode;
tail.nextNode=null;
}
map.put(key,node);
node.nextNode=head;
head.preNode=node;
head=node;
}
}
//删除当前被操作的节点并加入到链表的头部
private void removeAndInsert(Node tmp){
if(tmp.nextNode==null&&tmp.preNode!=null){
tail=tmp.preNode;
tmp.preNode.nextNode=null;
}
if(tmp.nextNode!=null&&tmp.preNode!=null){
tmp.preNode.nextNode=tmp.nextNode;
tmp.nextNode.preNode=tmp.preNode;
}
tmp.preNode=null;
tmp.nextNode=head;
head.preNode=tmp;
head=tmp;
}
}
2.利用LinkedHashMap实现LRU
LinkedHashMap是继承hashmap的基础上自己给节点增加了保存节点插入的顺序,有几个重要的方法需要了解下:
- afterNodeAccess(Node<K,V> e)这个方法hashmap是空方法并没有实现,而LinkedHashMap实现了这个方法用来把节点移动到最后(最新加入的位置)
- afterNodeInsertion(boolean evict)这个方法是在一定前提条件下会删除最老的节点,也就是头节点
- removeEldestEntry(first)这个方法是判断是否需要删除最老节点
//移动节点到尾部(尾部是保存最新的节点)
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
//在节点插入后的时候需不需要删除最老节点
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);
}
}
//是否删除最老的节点
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
//LinkedHashMap重写的方法
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)//
afterNodeAccess(e);
return e.value;
}
//LinkedHashMap使用的是hashmap的put方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);//这里会移动节点
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);//这里肯能会删除最老节点
return null;
}
所以我们想要实现LRU只需要简单的实现LinkedHashMap,并重写removeEldestEntry方法即可:
public class LRUCacheSimple<K,V> extends LinkedHashMap<K,V> {
private final int cache_size;
public LRUCacheSimple(int cacheSize){
super((int)Math.ceil(cacheSize/0.75)+1,0.75f,true);
cache_size=cacheSize;
}
protected boolean removeEldestEntry(Map.Entry<K,V> eldest){
return size()>cache_size;
}
}