【算法】—— LRU算法

LRU原理

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

实现1

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

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.Map;  

/**
 * 类说明:利用LinkedHashMap实现简单的缓存, 必须实现removeEldestEntry方法,具体参见JDK文档
 *
 * @author dennis
 *
 * @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);
    }

    public void put(K k,V v){
        CacheNode node = caches.get(k);
        if(node == null){
            if(caches.size() >= CacheCapcity){

                caches.remove(last.key);
                removeLast();
            }
            node = new CacheNode();
            node.key = k;
        }
        node.value = v;
        moveToFirst(node);
        caches.put(k, node);
    }

    public Object  get(K k){
        CacheNode node = caches.get(k);
        if(node == null){
            return null;
        }
        moveToFirst(node);
        return node.value;
    }

    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();
    }

    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());
    }

}

原文地址:https://www.cnblogs.com/bopo/p/9255654.html

时间: 2024-10-09 22:03:59

【算法】—— LRU算法的相关文章

最近最久未使用页面淘汰算法———LRU算法(java实现)

LRU算法,即Last Recently Used ---选择最后一次访问时间距离当前时间最长的一页并淘汰之--即淘汰最长时间没有使用的页 按照最多5块的内存分配情况,实现LRU算法代码如下: public class LRU { private int theArray[]; private int back; //定义队尾 private int currentSize; //队列中存放元素个数 private int maxSize=5; //队列中能存放元素的个数 public LRU(

缓存淘汰算法--LRU算法

1. LRU1.1. 原理 LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是"如果数据最近被访问过,那么将来被访问的几率也更高". 1.2. 实现 最常见的实现是使用一个链表保存缓存数据,详细算法实现如下: 1. 新数据插入到链表头部: 2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部: 3. 当链表满的时候,将链表尾部的数据丢弃. 1.3. 分析 [命中率] 当存在热点数据时,LRU的效率很好,但偶发性的

使用java.util.LinkedList模拟实现内存页面置换算法--LRU算法

一,LRU算法介绍 LRU算法是最近最少未使用算法.当内存缺页时,总是优先选出距离当前最久未使用的页面换出,并把当前的缺页换入.该算法可用栈模拟实现. 栈顶总是保存当前最近访问的页面号,栈底则总是保存最久未访问的页面号.对于下一个页面,有两种情况: ①命中,则需要:更新栈顶元素.即将当前命中的页面号放到栈顶. ②未命中,这里还需要考虑栈是否满了.1)若栈未满,直接将未命中的页面号放到栈顶即可.2)栈已经满了,则需要选中一页换出(栈底元素是最久未访问的页面),然后再将新页面放入栈顶. 二,代码实现

C语言实现FIFO算法与LRU算法

在操作系统中,当程序在运行过程中,若其所要访问的页面不再内存中而需要把他们调入内存,但内存已无空闲空间时,为了保证该进程能正常运行,系统必须从内存调出一页程序或数据送磁盘的兑换区中.但哪一个页面调出,须根据一定的算法确定.通常,把选择换出页面的算法称为页面置换算法(Page-Replacement Algorithms).置换算法的好坏将直接影响到系统的性能. 1) 先进先出(FIFO)页面置换算法 该算法总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面予以淘汰.该算法实现简单,只需

Redis的LRU算法

Redis的LRU算法 LRU算法背后的的思想在计算机科学中无处不在,它与程序的"局部性原理"很相似.在生产环境中,虽然有Redis内存使用告警,但是了解一下Redis的缓存使用策略还是很有好处的.下面是生产环境下Redis使用策略:最大可用内存限制为4GB,采用 allkeys-lru 删除策略.所谓删除策略:当redis使用已经达到了最大内存,比如4GB时,如果这时候再往redis里面添加新的Key,那么Redis将选择一个Key删除.那如何选择合适的Key删除呢? CONFIG

缓存置换策略-LRU算法

LRU算法 LRU算法定义: LRU算法是指最近最少使用算法,意思是LRU认为最近使用过的数据,将来被访问的概率会大,最近没有被访问的数据意味着以后刚问的概率小. 为何要用LRU算法: 1.我们的存储空间是有限的,当存储空间满了之后,要删除哪些数据呢,才能会时缓存的命中率高一些呢 2.LRU算法还是比较简单的. 算法: 最常见的算法是使用一个链表来实现 1)将新数据插入到表头 2)每当缓存命中时,将数据移动到表头 3)当l存储空间满了的时候将链表尾部的数据删除 缺点:链表实现,需要遍历链表找到命

操作系统 页面置换算法LRU和FIFO

LRU(Least Recently Used)最少使用页面置换算法,顾名思义,就是替换掉最少使用的页面. FIFO(first in first out,先进先出)页面置换算法,这是的最早出现的置换算法.该算法总是淘汰最先进入内存的页面,即选择在内存中驻留时间最长的页面给予淘汰. FIFO置换算法有这样一个奇怪现象:内存空间块数越多,缺页中断率可能相反的越高(缺页中断次数越高). LFU(Least Frequently Used)最近最少使用算法,它是基于“如果一个数据在最近一段时间内使用次

缓存算法–LRU

LRU LRU是Least Recently Used 的缩写,翻译过来就是“最近最少使用”,也就是说,LRU缓存把最近最少使用的数据移除,让给最新读取的数据.而往往最常读取的,也是读取次数最多的,所以,利用LRU缓存,我们能够提高系统的performance. LRU实现 1. 新数据插入到链表头部: 2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部: 3. 当链表满的时候,将链表尾部的数据丢弃. LRU分析 [命中率] 当存在热点数据时,LRU的效率很好,但偶发性的.周期性的批量

【leetcode】:LRU Cache_缓存淘汰算法LRU的设计与实现

内存淘汰算法是一个比较重要的算法,经常会被问道:如果让你设计一个LRU算法你会怎么做?尽可能的保持存取的高效.那么就依着这道算法题对LRU进行一个简单的介绍. 题目地址:https://oj.leetcode.com/problems/lru-cache/ 1.什么是LRU算法?为什么使用LRU? LRU即Least Recently Used的缩写,即最近最少使用的意思.我们知道在内存空间是有限的,那么当内存被数据占满的时候,而需要访问的数据又不在内存中,那么就需要将内存中的最少使用的数据淘汰

redis的LRU算法(一)

最近加班比较累,完全不想写作了.. 刚看到一篇有趣的文章,是redis的作者antirez对redis的LRU算法的回顾.LRU算法是Least Recently Used的意思,将最近最少使用的资源丢掉.Redis经常被用作cache,如果能够将不常用的key移除,尽量保留常用的,那内存的利用率就相当高了.当然,LRU也有弱点,考虑下面一种情况: ~~~~~A~~~~~A~~~~~A~~~~A~~~~~A~~~~~A~~| ~~B~~B~~B~~B~~B~~B~~B~~B~~B~~B~~B~~