LRU

 

LRU(least recently used)是将近期最不会访问的数据给淘汰掉,其实LRU是认为最近被使用过的数据,那么将来被访问的概率也多,最近没有被访问,那么将来被访问的概率也比较低“

LRU一般采用链表的放缓存实现,便于快速移动数据位置

最常见的实现是使用一个链表保存缓存数据,详细算法实现如下:

   1. 新数据插入到链表头部;
   2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
   3. 当链表满的时候,将链表尾部的数据丢弃。
分析
【命中率】
   当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重。
复杂度】
实现简单。
【代价】
   命中时需要遍历链表,找到命中的数据块索引,然后需要将数据移到头部。

/** 
 * 类说明:利用LinkedHashMap实现简单的缓存, 必须实现removeEldestEntry方法
 *  
 * @param <K> 
 * @param <V> 
 */ 
public class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> {  
    private final int maxCapacity;  

    private static final float DEFAULT_LOAD_FACTOR = 0.75f;  

    private final Lock lock = new ReentrantLock();  

    public LRULinkedHashMap(int maxCapacity) {  
        super(maxCapacity, DEFAULT_LOAD_FACTOR, true);  
        this.maxCapacity = maxCapacity;  
    }  

    @Override 
    protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {  
        return size() > maxCapacity;  
    }  
    @Override 
    public boolean containsKey(Object key) {  
        try {  
            lock.lock();  
            return super.containsKey(key);  
        } finally {  
            lock.unlock();  
        }  
    }  


    @Override 
    public V get(Object key) {  
        try {  
            lock.lock();  
            return super.get(key);  
        } finally {  
            lock.unlock();  
        }  
    }  

    @Override 
    public V put(K key, V value) {  
        try {  
            lock.lock();  
            return super.put(key, value);  
        } finally {  
            lock.unlock();  
        }  
    }  

    public int size() {  
        try {  
            lock.lock();  
            return super.size();  
        } finally {  
            lock.unlock();  
        }  
    }  

    public void clear() {  
        try {  
            lock.lock();  
            super.clear();  
        } finally {  
            lock.unlock();  
        }  
    }  

    public Collection<Map.Entry<K, V>> getAll() {  
        try {  
            lock.lock();  
            return new ArrayList<Map.Entry<K, V>>(super.entrySet());  
        } finally {  
            lock.unlock();  
        }  
    }  
}  

实现2

      LRUCache的链表+HashMap实现 

传统意义的LRU算法是为每一个Cache对象设置一个计数器,每次Cache命中则给计数器+1,而Cache用完,需要淘汰旧内容,放置新内容时,就查看所有的计数器,并将最少使用的内容替换掉。弊端很明显,如果Cache的数量少,问题不会很大, 但是Cache的空间过大,达到10W或者100W以上,一旦需要淘汰,则需要遍历所有计算器,其性能与资源消耗是巨大的。效率也就非常的慢了。

 

原理: 将Cache的所有位置都用双链表连接起来,当一个位置被命中之后,就将通过调整链表的指向,将该位置调整到链表头的位置,新加入的Cache直接加到链表头中
这样,在多次进行Cache操作后,最近被命中的,就会被向链表头方向移动,而没有命中的,而想链表后面移动,链表尾则表示最近最少使用的Cache。
当需要替换内容时候,链表的最后位置就是最少被命中的位置,只需要淘汰链表最后的部分即可。


代码来实现一个LRU策略的缓存:

import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;


public class LRUCache<K, V> {

    private int currentCacheSize;
    private int CacheCapcity;
    private HashMap<K,CacheNode> caches;
    private CacheNode first;
    private CacheNode last;

    public LRUCache(int size){
        currentCacheSize = 0;
        this.CacheCapcity = size;
        caches = new HashMap<K,CacheNode>(size);
    }
    //存放值会将判断是否超出map表的容量,超出需要进行移除最少的访问的节点
//存放节点需要同时存放链表与map的数据结构中
public void put(K k,V v){ CacheNode node = caches.get(k); //从对应的map中取出node对象 if(node == null){ if(caches.size() >= CacheCapcity){ caches.remove(last.key); removeLast(); } node = new CacheNode(); node.key = k; } //存在node对象或创建的node,覆盖值,移到队列的头部,并放入map结构中 node.value = v; moveToFirst(node); caches.put(k, node); } //取值从map中根据key进行取出,存在将节点放入链表的头部 public Object get(K k){ CacheNode node = caches.get(k); if(node == null){ return null; } moveToFirst(node); return node.value; } //移除需要从链表中与map表中移除,链表需要考虑首尾节点 public Object remove(K k){ CacheNode node = caches.get(k); if(node != null){ if(node.pre != null){ node.pre.next=node.next; } if(node.next != null){ node.next.pre=node.pre; } if(node == first){ first = node.next; } if(node == last){ last = node.pre; } } return caches.remove(k); } public void clear(){ first = null; last = null; caches.clear(); } //移到首节点,需要考虑链表首尾节点与null的情况,移动到首节点,只移动链表结构 private void moveToFirst(CacheNode node){ if(first == node){ return; } if(node.next != null){ node.next.pre = node.pre; } if(node.pre != null){ node.pre.next = node.next; } if(node == last){ last= last.pre; } if(first == null || last == null){ first = last = node; return; } node.next=first; first.pre = node; first = node; first.pre=null; } private void removeLast(){ if(last != null){ last = last.pre; if(last == null){ first = null; }else{ last.next = null; } } } @Override public String toString(){ StringBuilder sb = new StringBuilder(); CacheNode node = first; while(node != null){ sb.append(String.format("%s:%s ", node.key,node.value)); node = node.next; } return sb.toString(); } class CacheNode{ CacheNode pre; CacheNode next; Object key; Object value; public CacheNode(){ } } public static void main(String[] args) { LRUCache<Integer,String> lru = new LRUCache<Integer,String>(3); lru.put(1, "a"); // 1:a System.out.println(lru.toString()); lru.put(2, "b"); // 2:b 1:a System.out.println(lru.toString()); lru.put(3, "c"); // 3:c 2:b 1:a System.out.println(lru.toString()); lru.put(4, "d"); // 4:d 3:c 2:b System.out.println(lru.toString()); lru.put(1, "aa"); // 1:aa 4:d 3:c System.out.println(lru.toString()); lru.put(2, "bb"); // 2:bb 1:aa 4:d System.out.println(lru.toString()); lru.put(5, "e"); // 5:e 2:bb 1:aa System.out.println(lru.toString()); lru.get(1); // 1:aa 5:e 2:bb System.out.println(lru.toString()); lru.remove(11); // 1:aa 5:e 2:bb System.out.println(lru.toString()); lru.remove(1); //5:e 2:bb System.out.println(lru.toString()); lru.put(1, "aaa"); //1:aaa 5:e 2:bb System.out.println(lru.toString()); } }

FIFO实现: 
LinkedHashMap默认就是按存储的数据排序的,满足先进先出规则,所以可以继承LinkedHashMap实现基于FIFO的缓存机制:

import java.util.LinkedHashMap;

public class FIFOCache<K,V> extends LinkedHashMap<K, V>{

    private static final long serialVersionUID = 436014030358073695L;

    private final int SIZE;

    public FIFOCache(int size) {
        super();//调用父类无参构造,不启用LRU规则
        SIZE = size;
    }

    //重写淘汰机制
    @Override
    protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
        return size() > SIZE;  //如果缓存存储达到最大值删除最后一个
    }


}

 

LFU(Least Frequently Used): 
最不常使用,就是对存储的数据都会有一个计数引用,然后队列按数据引用次数排序,引用数多的排在最前面,引用数少的排在后面。如果这个时候有新的数据进来,把最后面的数据删除,把新进数据排在最后面,且引用次数为1

 

 

 

 

 

参考:

https://blog.csdn.net/xiao_mrs_li/article/details/69257694

https://blog.csdn.net/wangxilong1991/article/details/70172302

https://blog.csdn.net/u012403290/article/details/68926201

http://flychao88.iteye.com/blog/1977653(深文)

posted on 2018-10-13 17:06  溪水静幽  阅读(191)  评论(0)    收藏  举报