LRU与LFU

一、概念

LRU(Least Recently Used,最近最少使用)算法和

LFU(Least Frequently Used,最不经常使用)算法

都是用来作为缓存淘汰机制,保证缓存空间的有效利用。

二、使用场景

适合使用 LRU 的场景:

  • 适合使用 LRU 的场景是数据访问具有时间局部性的场景。
  • 例如在Web网站中,用户最近访问的页面,很可能再次被访问。在这种情况下,缓存的内容一般都是最近访问的页面数据,而离当前时间较长的访问页面数据缓存命中率较小,很可能被清理出缓存,因此使用 LRU 算法清理缓存会比较合适。
  • 另一个适合使用 LRU 的场景是数据库缓存。通常情况下,数据库表不会突然新增大量的记录导致缓存不够用,而是查询热点数据集中在很少几张表上。在这种情况下,使用 LRU 算法淘汰缓存可以比 LFU 更好地利用 LRU 的时间局部性,将数据进入缓存,提高缓存命中率。

适合使用 LFU 的场景:

  • 适合使用 LFU 的场景是数据访问热度不均匀的场景。
  • 例如在电商网站的热门商品推荐、搜索引擎中的热门搜索推荐等场景中,只有少量的热门数据集中访问比较多,而大量的数据很少被访问,使用 LRU 算法会出现所谓的「冷用户」现象,这时候使用 LFU 算法就能很好的有效清理低访问率的数据。
  • 另一个适合使用 LFU 的场景是数据库缓存。当数据集合较大,且访问热度比较分散的时候,使用 LRU 的效果较差,因为 LRU 显然更倾向于缓存最近的数据,而 LFU 会更倾向于缓存访问频率高的数据。

综上,根据访问局部性和热度分布的不同,不同的应用场景会选择不同的缓存淘汰算法,以达到最优的缓存利用效果。

三、代码实现

LRU算法实现

import java.util.LinkedHashMap;
import java.util.Map;

public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int MAX_CACHE_SIZE;  // 缓存最大容量

    public LRUCache(int maxCacheSize) {
        // 第三个参数 accessOrder=true表示按照访问顺序而非插入顺序排序。
        super((int) Math.ceil(maxCacheSize / 0.75) + 1, 0.75f, true);
        MAX_CACHE_SIZE = maxCacheSize;
    }

    // 在LRUCache类中,我们通过覆盖removeEldestEntry方法来实现LRU算法的删除策略,当缓存元素长度超过设定的最大容量时,就将最久未使用的元素删除。
    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > MAX_CACHE_SIZE;
    }

    public static void main(String[] args) {
        LRUCache<String, String> cache = new LRUCache<>(3);
        cache.put("1", "one");
        cache.put("2", "two");
        cache.put("3", "three");
        System.out.println(cache);

        cache.get("2");
        cache.put("4", "four");
        System.out.println(cache);

        cache.put("3", "new-three");
        System.out.println(cache);
    }
}

输出:

{1=one, 2=two, 3=three}
{2=two, 4=four, 3=three}
{2=two, 4=four, 3=new-three}

LFU算法实现

import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;

public class LFUCache {

    // 记录key对应的value
    private Map<Integer, Integer> valueMap = new HashMap<>();
    // 记录key键的访问频率
    private Map<Integer, Integer> freqMap = new HashMap<>();
    // 记录每个频率下的key,使用LinkedHashSet保证添加和删除的效率
    private Map<Integer, LinkedHashSet<Integer>> freqKeys = new HashMap<>();
    // LFU缓存容量
    private int capacity;
    // 最小频率
    private int minFreq = 0;

    /**
     * LFU缓存构造函数
     * @param capacity 缓存容量
     */
    public LFUCache(int capacity) {
        this.capacity = capacity;
    }

    /**
     * 获取LFU缓存中对应key的value值
     * @param key 键
     * @return 对应的值
     */
    public int get(int key) {
        // 如果缓存中没有这个key,返回-1
        if (!valueMap.containsKey(key)) {
            return -1;
        }
        // 更新key的访问频率并返回value值
        int freq = freqMap.get(key);
        freqMap.put(key, freq + 1);
        freqKeys.get(freq).remove(key);
        if (freq == minFreq && freqKeys.get(freq).isEmpty()) {
            // 如果刚刚移除的是最小的频率,更新最小频率
            minFreq++;
        }
        freqKeys.computeIfAbsent(freq + 1, l -> new LinkedHashSet<>()).add(key);
        return valueMap.get(key);
    }

    /**
     * 向LFU缓存中添加键值对
     * @param key 键
     * @param value 值
     */
    public void put(int key, int value) {
        if (capacity <= 0) {
            return;
        }
        // 如果缓存中已经有这个key,更新value值
        if (valueMap.containsKey(key)) {
            valueMap.put(key, value);
            get(key);
            return;
        }
        // 缓存已满,移除访问频率最低的key
        if (valueMap.size() >= capacity) {
            int toRemove = freqKeys.get(minFreq).iterator().next();
            freqKeys.get(minFreq).remove(toRemove);
            valueMap.remove(toRemove);
            freqMap.remove(toRemove);
        }
        // 添加新的键值对
        valueMap.put(key, value);
        freqMap.put(key, 1);
        freqKeys.computeIfAbsent(1, l -> new LinkedHashSet<>()).add(key);
        minFreq = 1;
    }

    /**
     * 测试用例
     * @param args 参数
     */
    public static void main(String[] args) {
        LFUCache cache = new LFUCache(3);
        cache.put(1, 1);
        cache.put(2, 2);
        System.out.println(cache.get(1));    // 1
        cache.put(3, 3);
        System.out.println(cache.get(2));    // -1
        System.out.println(cache.get(3));    // 3
        cache.put(4, 4);
        System.out.println(cache.get(1));    // -1
        System.out.println(cache.get(3));    // 3
        System.out.println(cache.get(4));    // 4
    }
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值