缓存淘汰算法--LRU算法

分析&回答


LRU定义与实现

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。详细算法实现如下:

缓存淘汰算法--LRU算法_第1张图片

优缺点:

  • 当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重。
  • 命中时需要遍历链表,找到命中的数据块索引,然后需要将数据移到头部。

LRU-K定义与实现

LRU-K中的K代表最近使用的次数,因此LRU可以认为是LRU-1。LRU-K的主要目的是为了解决LRU算法“缓存污染”的问题,其核心思想是将“最近使用过1次”的判断标准扩展为“最近使用过K次”。详细算法实现如下:

缓存淘汰算法--LRU算法_第2张图片

  1. 数据第一次被访问,加入到访问历史列表;
  2. 如果数据在访问历史列表里后没有达到K次访问,则按照一定规则(FIFO,LRU)淘汰;
  3. 当访问历史队列中的数据访问次数达到K次后,将数据索引从历史队列删除,将数据移到缓存队列中,并缓存此数据,缓存队列重新按照时间排序;
  4. 缓存数据队列中被再次访问后,重新排序;
  5. 需要淘汰数据时,淘汰缓存队列中排在末尾的数据,即:淘汰“倒数第K次访问离现在最久”的数据。

优缺点:

  • LRU-K降低了“缓存污染”带来的问题,命中率比LRU要高。LRU-K队列是一个优先级队列,算法复杂度和代价比较高。
  • 由于LRU-K还需要记录那些被访问过、但还没有放入缓存的对象,因此内存消耗会比LRU要多;当数据量很大的时候,内存消耗会比较可观。
  • LRU-K需要基于时间进行排序(可以需要淘汰时再排序,也可以即时排序),CPU消耗比LRU要高。

基于双链表 的LRU实现

public class LRUCache {  

    private int cacheSize;//缓存大小  
    private Hashtable nodes;//缓存容器  
    private int currentSize;//当前缓存对象数量  
    private CacheNode first;//(实现双链表)链表头  
    private CacheNode last;//(实现双链表)链表尾  
   
    /** 
     * 链表节点 
     * @author Administrator 
     * 
     */  
    class CacheNode {  
        CacheNode prev;//前一节点  
        CacheNode next;//后一节点  
        Object value;//值  
        Object key;//键  
        CacheNode() {  
        }  
    }  
    public LRUCache(int i) {  
        currentSize = 0;  
        cacheSize = i;  
        nodes = new Hashtable(i);//缓存容器  
    }  
      
    /** 
     * 获取缓存中对象 
     * @param key 
     * @return 
     */  
    public Object get(Object key) {  
        CacheNode node = (CacheNode) nodes.get(key);  
        if (node != null) {  
            moveToHead(node);  
            return node.value;  
        } else {  
            return null;  
        }  
    }  
      
    /** 
     * 添加缓存 
     * @param key 
     * @param value 
     */  
    public void put(Object key, Object value) {  
        CacheNode node = (CacheNode) nodes.get(key);  
          
        if (node == null) {  
            //缓存容器是否已经超过大小.  
            if (currentSize >= cacheSize) {  
                if (last != null)//将最少使用的删除  
                    nodes.remove(last.key);  
                removeLast();  
            } else {  
                currentSize++;  
            }  
              
            node = new CacheNode();  
        }  
        node.value = value;  
        node.key = key;  
        //将最新使用的节点放到链表头,表示最新使用的.  
        moveToHead(node);  
        nodes.put(key, node);  
    }  
    /** 
     * 将缓存删除 
     * @param key 
     * @return 
     */  
    public Object remove(Object key) {  
        CacheNode node = (CacheNode) nodes.get(key);  
        if (node != null) {  
            if (node.prev != null) {  
                node.prev.next = node.next;  
            }  
            if (node.next != null) {  
                node.next.prev = node.prev;  
            }  
            if (last == node)  
                last = node.prev;  
            if (first == node)  
                first = node.next;  
        }  
        return node;  
    }  
    public void clear() {  
        first = null;  
        last = null;  
    }  
    /** 
     * 删除链表尾部节点 
     *  表示 删除最少使用的缓存对象 
     */  
    private void removeLast() {  
        //链表尾不为空,则将链表尾指向null. 删除连表尾(删除最少使用的缓存对象)  
        if (last != null) {  
            if (last.prev != null)  
                last.prev.next = null;  
            else  
                first = null;  
            last = last.prev;  
        }  
    }  
      
    /** 
     * 移动到链表头,表示这个节点是最新使用过的 
     * @param node 
     */  
    private void moveToHead(CacheNode node) {  
        if (node == first)  
            return;  
        if (node.prev != null)  
            node.prev.next = node.next;  
        if (node.next != null)  
            node.next.prev = node.prev;  
        if (last == node)  
            last = node.prev;  
        if (first != null) {  
            node.next = first;  
            first.prev = node;  
        }  
        first = node;  
        node.prev = null;  
        if (last == null)  
            last = first;  
    }   
}

反思&扩展


喵呜面试助手: 一站式解决面试问题,你可以搜索微信小程序 [喵呜面试助手] 或关注 [喵呜刷题] -> 面试助手 免费刷题。如有好的面试知识或技巧期待您的共享!

你可能感兴趣的:(算法基础,java,算法,面试)