缓存算法(FIFO 、LRU、LFU三种算法的区别)

FIFO算法

FIFO 算法是一种比较容易实现的算法。它的思想是先进先出(FIFO,队列),这是最简单、最公平的一种思想,即若是一个数据是最早进入的,那么能够认为在未来它被访问的可能性很小。空间满的时候,最早进入的数据会被最先置换(淘汰)掉html

FIFO 算法的描述:设计一种缓存结构,该结构在构造时肯定大小,假设大小为 K,并有两个功能:java

  1. set(key,value):将记录(key,value)插入该结构。当缓存满时,将最早进入缓存的数据置换掉。
  2. get(key):返回key对应的value值。

实现:维护一个FIFO队列,按照时间顺序将各数据(已分配页面)连接起来组成队列,并将置换指针指向队列的队首。再进行置换时,只需把置换指针所指的数据(页面)顺次换出,并把新加入的数据插到队尾便可。git

缺点:判断一个页面置换算法优劣的指标就是缺页率,而FIFO算法的一个显著的缺点是,在某些特定的时刻,缺页率反而会随着分配页面的增长而增长,这称为Belady现象。产生Belady现象现象的缘由是,FIFO置换算法与进程访问内存的动态特征是不相容的,被置换的内存页面每每是被频繁访问的,或者没有给进程分配足够的页面,所以FIFO算法会使一些页面频繁地被替换和从新申请内存,从而致使缺页率增长。所以,如今再也不使用FIFO算法github

LRU算法

LRU(The Least Recently Used,最近最久未使用算法)是一种常见的缓存算法,在不少分布式缓存系统(如Redis, Memcached)中都有普遍使用。算法

LRU算法的思想是:若是一个数据在最近一段时间没有被访问到,那么能够认为在未来它被访问的可能性也很小。所以,当空间满时,最久没有访问的数据最早被置换(淘汰)数组

LRU算法的描述: 设计一种缓存结构,该结构在构造时肯定大小,假设大小为 K,并有两个功能:缓存

  1. set(key,value):将记录(key,value)插入该结构。当缓存满时,将最久未使用的数据置换掉。
  2. get(key):返回key对应的value值。

实现:最朴素的思想就是用数组+时间戳的方式,不过这样作效率较低。所以,咱们能够用双向链表(LinkedList)+哈希表(HashMap)实现(链表用来表示位置,哈希表用来存储和查找),在Java里有对应的数据结构LinkedHashMap数据结构

LInkedHashMap

利用JavaLinkedHashMap用很是简单的代码来实现基于LRU算法的Cache功能分布式

import java.util.LinkedHashMap;
import java.util.Map;
/**
 * 简单用LinkedHashMap来实现的LRU算法的缓存
 */
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private int cacheSize;
    public LRUCache(int cacheSize) {
        super(16, (float) 0.75, true);
        this.cacheSize = cacheSize;
    }
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > cacheSize;
    }
}

测试:post

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LRUCacheTest {
    private static final Logger log = LoggerFactory.getLogger(LRUCacheTest.class);
    private static LRUCache<String, Integer> cache = new LRUCache<>(10);

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            cache.put("k" + i, i);
        }
        log.info("all cache :'{}'",cache);
        cache.get("k3");
        log.info("get k3 :'{}'", cache);
        cache.get("k4");
        log.info("get k4 :'{}'", cache);
        cache.get("k4");
        log.info("get k4 :'{}'", cache);
        cache.put("k" + 10, 10);
        log.info("After running the LRU algorithm cache :'{}'", cache);
    }
}

Output:

all cache :'{k0=0, k1=1, k2=2, k3=3, k4=4, k5=5, k6=6, k7=7, k8=8, k9=9}'
get k3 :'{k0=0, k1=1, k2=2, k4=4, k5=5, k6=6, k7=7, k8=8, k9=9, k3=3}'
get k4 :'{k0=0, k1=1, k2=2, k5=5, k6=6, k7=7, k8=8, k9=9, k3=3, k4=4}'
get k4 :'{k0=0, k1=1, k2=2, k5=5, k6=6, k7=7, k8=8, k9=9, k3=3, k4=4}'
After running the LRU algorithm cache :'{k1=1, k2=2, k5=5, k6=6, k7=7, k8=8, k9=9, k3=3, k4=4, k10=10}'

LFU算法

LFU(Least Frequently Used ,最近最少使用算法)也是一种常见的缓存算法。

顾名思义,LFU算法的思想是:若是一个数据在最近一段时间不多被访问到,那么能够认为在未来它被访问的可能性也很小。所以,当空间满时,最小频率访问的数据最早被淘汰

LFU 算法的描述:

设计一种缓存结构,该结构在构造时肯定大小,假设大小为 K,并有两个功能:

  1. set(key,value):将记录(key,value)插入该结构。当缓存满时,将访问频率最低的数据置换掉。
  2. get(key):返回key对应的value值。

算法实现策略:考虑到 LFU 会淘汰访问频率最小的数据,咱们须要一种合适的方法按大小顺序维护数据访问的频率。LFU 算法本质上能够看作是一个 top K 问题(K = 1),即选出频率最小的元素,所以咱们很容易想到能够用二项堆来选择频率最小的元素,这样的实现比较高效。最终实现策略为小顶堆+哈希表。

参考:

缓存算法(页面置换算法)总结

谈谈缓存和基本的缓存算法

基于LinkedHashMap的LRUCache实现

Java集合详解5:深刻理解LinkedHashMap和LRU缓存

相关文章
相关标签/搜索