深入了解缓存算法:FIFO 、LFU 和 LRU 的原理及实现

深入了解缓存算法:FIFO 、LFU 和 LRU 的原理及实现

缓存算法在优化数据访问速度和系统性能中扮演着关键角色。不同的缓存策略适用于不同的场景,了解它们的原理和实现方式可以帮助我们做出更好的选择。本文将介绍三种常见的缓存算法:LRU(最近最少使用)、LFU(最少使用)和 FIFO(先进先出),并提供 Java 实现示例。

1. FIFO(先进先出)算法

原理: FIFO(First-In-First-Out)算法是最简单的缓存替换策略。它按照数据进入缓存的顺序来决定替换的数据。最早进入缓存的数据在缓存满时会被移除。

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;

/*
    FIFO 先进先出  缓存算法  队列实现
    K: 缓存中数据的键的类型
    V: 缓存中数据的值的类型
    使用泛型  实现缓存算法  提高代码的复用性和类型安全性
 */
public class FIFOCache<K,V> {
    private final int capacity;  // 缓存的最大容量
    private final Queue<K> cache;  // 存储缓存键的队列 使用LinkedList实现
    private final Map<K,V> map;  // 存储缓存键值对的映射

    /*
        构造函数 构造函数,初始化 FIFO 缓存。
        capacity 缓存的最大容量
     */
    public  FIFOCache(int capacity) {
        this.capacity = capacity;
        this.cache = new LinkedList<>();
        this.map = new HashMap<>();
    }

    /*
        根据键获取缓存中的值。 如果键不存在,则返回 null。
     */
    public V get(K key) {
        return map.get(key);
    }
    /*
        将键值对添加到缓存中。 如果缓存已满,则移除最早添加的键值对。
     */
    public void put(K key, V value) {
        //如果缓存已满 移除最早添加的键值对
        if (cache.size() >= capacity) {
            K oldestKey = cache.poll(); // 移除最早添加的键
            map.remove(oldestKey); // 移除最早添加的键值对
        }
        cache.offer(key); // 添加新键到队列末尾
        map.put(key, value); // 添加新键值对到映射中
    }
}

特点:

  • 优点: 实现简单,易于理解和操作。
  • 缺点: 可能会丢弃最近刚刚使用的数据,不能有效利用缓存空间。
2. LFU(最少使用)算法

原理: LFU(Least Frequently Used)算法根据数据的访问频率来决定缓存数据的替换。最少被访问的数据会被移除。

import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;

/*
    LFU 缓存算法  最少使用算法
    K: 缓存中数据的键的类型
    V: 缓存中数据的值的类型
 */
public class LFUCache<K,V> {
    private final int capacity;  // 缓存的最大容量
    private final Map<K,V> map;  // 存储缓存键值对的映射
    private final Map<K,Integer> freqMap;  // 记录每个键的访问频率
    private final PriorityQueue<K> priorityQueue;  // 优先队列,用于根据访问频率排序键

    /*
        构造函数 构造函数,初始化 LFU 缓存。
        capacity 缓存的最大容量
     */
    public LFUCache(int capacity) {
        this.capacity = capacity;
        this.map = new HashMap<>();
        this.freqMap = new HashMap<>();
        // 按照访问频率升序排序的优先队列 Lambda表达式,比较两个键的访问频率 比较器
        this.priorityQueue = new PriorityQueue<>((k1,k2)-> freqMap.get(k1) - freqMap.get(k2));
    }

    /*
        根据键获取缓存中的值。 如果键不存在,则返回 null。
        每次访问键时,更新键的访问频率。
     */
    public V get(K key) {
        // 检查键是否存在于缓存中
        if (!map.containsKey(key)) {
            return null;
        }
        // 更新键的访问频率
        freqMap.put(key,freqMap.get(key) + 1);
        // 从优先队列中移除旧的键
        priorityQueue.remove(key);
        // 重新排序优先队列
        priorityQueue.offer(key);
        // 返回键对应的值
        return map.get(key);
    }

    /*
        将键值对添加到缓存中。 如果缓存已满,则移除访问频率最低的键值对。
     */
    public void put(K key, V value) {
        //如果缓存容量为 0,直接返回
        if (capacity<=0) return;

        // 如果缓存已满 移除访问频率最低的键值对
        if (map.size() >= capacity) {
            K leastFreqKey = priorityQueue.poll(); // 获取并移除使用频率最少的键
            map.remove(leastFreqKey);  // 从缓存中移除
            freqMap.remove(leastFreqKey); // 移除访问频率最低的键的频率
        }

        // 添加新键值对到缓存中
        map.put(key,value);
        freqMap.put(key,1);  // 新键的访问频率为 1
        priorityQueue.offer(key);  // 将新键添加到优先队列中
    }

}

特点:

  • 优点: 有效保留使用频繁的数据。
  • 缺点: 实现较复杂,可能需要维护额外的数据结构。
3. LRU(最近最少使用)算法

原理: LRU(Least Recently Used)算法根据数据的最近使用情况来决定缓存数据的替换。最近最少使用的数据会被移除。

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

/*
    LRU 缓存算法  最近最少使用算法
    K: 缓存中数据的键的类型
    V: 缓存中数据的值的类型
    使用泛型  实现缓存算法  提高代码的复用性和类型安全性
 */
public class LRUCache<K,V> {
    private final int capacity;  // 缓存的最大容量
    private final LinkedHashMap<K,V> linkedHashMap;  // 双向链表,用于记录键的访问顺序
    /*
        构造函数 构造函数,初始化 LRU 缓存。
        capacity 缓存的最大容量
     */
    public LRUCache(int capacity) {
        this.capacity = capacity;
        // 初始化 LinkedHashMap,并设置访问顺序为 true 以支持 LRU 策略 表示按访问顺序排序
        this.linkedHashMap = new LinkedHashMap<>(capacity, 0.75f, true) {
            // 当缓存的元素个数超过最大容量时,移除最久未使用的元素
            @Override
            protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
                return size() > capacity;
            }
        };
    }
    /*
       将指定的键值对添加到缓存中。如果缓存已满,最久未使用的元素将被淘汰。
     */
    public void put(K key, V value) {
        linkedHashMap.put(key, value);
    }
    /*
        根据指定的键获取缓存中的值。如果键不存在,则返回 null。
     */
    public V get(K key) {
        return linkedHashMap.get(key);
    }
}

特点:

  • 优点: 能够有效处理时间局部性问题,保证缓存中的数据是最近使用的。
  • 缺点: 对链表操作有一定开销,但实现相对简单。

总结

选择合适的缓存算法可以显著提高系统性能。FIFO 算法简单易懂,但可能丢弃最近刚刚使用的数据;LFU 算法能够有效保留频繁使用的数据,但实现复杂;LRU 算法在处理时间局部性方面表现优异,且实现相对简单。根据具体需求选择合适的算法,可以帮助你更好地优化缓存系统。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值