我是靠谱客的博主 完美哈密瓜,最近开发中收集的这篇文章主要介绍算法之LRU和LFU算法,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

在地址映射过程中,若在页面中发现所要访问的页面不在内存中,则产生缺页中断。当发生缺页中断时,如果操作系统内存中没有空闲页面,则操作系统必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。而用来选择淘汰哪一页的规则叫做页面置换算法。

评价一个缓存算法好坏的标准主要有两个,一是命中率要高,二是算法要容易实现。当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重。LFU效率要优于LRU,且能够避免周期性或者偶发性的操作导致缓存命中率下降的问题。但LFU需要记录数据的历史访问记录,一旦数据访问模式改变,LFU需要更长时间来适用新的访问模式,即:LFU存在历史数据影响将来数据的“缓存污染”效用。FIFO虽然实现很简单,但是命中率很低,实际上也很少使用这种算法。

1、LRU

最近最少使用,淘汰最近不使用的页面
LRU表示最近最久未使用算法,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。即LRU算法是与每个页面最后使用的时间有关的。
1. 新数据插入到链表头部;
2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
3. 当链表满的时候,将链表尾部的数据丢弃。

以下为利用双向链表和HashMap实现LRU的代码,算法时间复杂度为O(1)(备注:其实也可以直接使用LinkedHashMap实现LRU)

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

public class LRUCache<K, V> {
    //存储的单位元素
    class CacheNode{
        Object key;
        Object value;
        CacheNode pre;//用于构造双向链表
        CacheNode next;
        public CacheNode(Object key,Object value){
            this.key = key;
            this.value = value;
        }
        public CacheNode(){
        }
    }
    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);
    }
    //添加新的节点时,放入链表头部
    public void put(K k,V v){
        CacheNode node = caches.get(k);
        if(node == null){
            if(caches.size() >= CacheCapcity){
                removeLast();
                caches.remove(last.key);
            }
            node = new CacheNode(k,v);
            caches.put(k, node);
            InsetToFirst(node);
        }else{
            moveToFirst(node);
        }
        currentCacheSize = caches.size();
    }
    //访问某个元素
    public Object  get(K k){
        CacheNode node = caches.get(k);
        if(node == null){
            return null;
        }
        moveToFirst(node);
        return node.value;
    }
    //将链表中的某个元素移到链表头部
    private void moveToFirst(CacheNode node){
        CacheNode prenode = node.pre;
        CacheNode nextnode = node.next;
        if(prenode == null && nextnode == null){
            return;
        }else if(nextnode == null){
            InsetToFirst(node);
            return;
        }else if(prenode == null){
            return;
        }else{
            prenode.next = nextnode;
            nextnode.pre = prenode;
            InsetToFirst(node);
        }
    }
    //插入某个元素到链表头部
    private void InsetToFirst(CacheNode node){
        if(first == null || last == null){
            first = node;
            last = node;
        }else{
        node.next = first;
        first.pre = node;
        }
    }
    //删除某个尾节点元素
    private void removeLast(){
        if(last != null){
            last = last.pre;
            if(last == null){
                first = null;
            }else{
                last.next = null;
            }
        }
    }
     //清空所有元素
    public void clear(){
        first = null;
        last = null;
        caches.clear();
    }
    @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();
    }
    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());
    }
}

1、LFU

最近使用次数最少, 淘汰使用次数最少的页面
LFU(最不经常使用算法)算法根据数据的历史访问频率来淘汰数据,其原理是如果数据过去被访问次数越多,将来被访问的几概率相对比较高。LFU的每个数据块都有一个引用计数,所有数据块按照引用计数排序,具有相同引用计数的数据块则按照时间排序。
具体算法如下:
1. 新加入数据插入到队列尾部(因为引用计数为1);
2. 队列中的数据被访问后,引用计数增加,队列重新排序;
3. 当需要淘汰数据时,将已经排序的列表最后的数据块删除;

以下为实现LFU的代码,算法时间复杂度为O(1),强烈推荐:
使用了两层链表,第一层链表按照历史访问频率排序,第二层链表按照访问时间排序,这样在插入节点的时候就不需要遍历操作
https://www.jianshu.com/p/437f53341f67

最后

以上就是完美哈密瓜为你收集整理的算法之LRU和LFU算法的全部内容,希望文章能够帮你解决算法之LRU和LFU算法所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(51)

评论列表共有 0 条评论

立即
投稿
返回
顶部