算法redis 高级应用java 基本功

Java实现FIFO/LRU缓存

2017-03-06  本文已影响124人  唐影若凡

声明:原创文章,转载请注明出处。https://www.jianshu.com/p/33e572da4b58

一、概述

在学操作系统的时候,会接触到页面缓存调度算法。缓存不可能是无限大的,所以会涉及到一些置换策略,来保证缓存的命中率。常见的有:FIFO、LRU、LFU、OPT策略等。

1、缓存置换算法

2、缓存置换算法的要素

3、Java LinkedHashMap简介

先看下LinkedHashMap的构造函数,三个参数分别为:初始化大小、装载因子和访问顺序。

public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

根据链表中元素的顺序可以分为:按插入顺序的链表(默认,false),和按访问顺序的链表(调用get方法)。默认是按插入顺序排序,如果指定按访问顺序排序,那么调用get方法后,会将这次访问的元素移至链表尾部,不断访问可以形成按访问顺序排序的链表。 可以重写removeEldestEntry方法返回true值指定插入元素时移除最老的元素。

结论1:可以得出accessOrder = true时,可以模仿出LRU的特性;accessOrder = false时,可以模仿出FIFO的特性。即满足缓存置换算法要素3。

这是LinkedHashMap中另外一个方法:移除最久的元素。当返回为false时,不会移除其中最久的元素。当返回true的时候,就会remove其中最久的元素。

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }

结论2:由于缓存置换算法要素2,当缓存满了之后,需通过返回true删除最久未被使用的元素。所以我们需要 重写此方法来删除缓存元素,达到缓存置换的要求。

当然,我们还需要满足缓存置换算法要素1,就大功告成了。由于Java Map是自动扩容的,当其table.size() > Capacity * loadFactor的时,会自动进行两倍扩容。

结论:为了使缓存能固定大小,需要禁止Map的自动扩容。可将初始化大小设置为(cacheSize / loadFactor) + 1,就可以在元素数目达到缓存大小时,不会自动扩容,达到缓存置换的要求。

二、实现简单的FIFO缓存

1、继承继承LinkedHashMap

public class FIFOCache<K, V> extends LinkedHashMap<K, V> {
    private static int MAX_CACHE_SIZE;

    public FIFOCache(int maxCacheSize) {
        super((int) Math.ceil(maxCacheSize / 0.75) + 1, 0.75f, false);
        this.MAX_CACHE_SIZE = maxCacheSize;
    }

    @Override
    public boolean removeEldestEntry(Map.Entry eldest) {
        return size() > MAX_CACHE_SIZE;
    }
}

2、根据LinkedHashMap重新实现

由于LinkedHashMap并非是线程安全的,我们可以仅利用LinkedHashMap的特性自己实现一个。

public class FIFOCache<K, V> {
    private static int MAX_CACHE_SIZE = 0;
    private final float LOAD_FACTORY = 0.75f;

    Map<K, V> map;

    public FIFOCache(int maxCacheSize) {
        this.MAX_CACHE_SIZE = maxCacheSize;
        // 根据 cacheSize 和 填充因子计算cache的容量
        int capacity = (int) Math.ceil(MAX_CACHE_SIZE / LOAD_FACTORY) + 1;
        map = new LinkedHashMap<K, V>(capacity, LOAD_FACTORY, false) {
            @Override
            protected boolean removeEldestEntry(Map.Entry eldest) {
                return size() > MAX_CACHE_SIZE;
            }
        };
    }

    public synchronized void put(K key, V value) {
        map.put(key, value);
    }

    public synchronized V get(K key) {
        return map.get(key);
    }

    public synchronized void remove(K key) {
        map.remove(key);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<K, V> entry : map.entrySet()) {
            sb.append(entry.getKey()).append("=")
                    .append(entry.getValue()).append(" ");
        }
        return sb.toString();
    }
}

三、实现简单的LRU缓存

1、继承LinkedHashMap

和FIFO的实现基本一致,只需要将accessOrder = false

public class Cache<K, V> extends LinkedHashMap<K, V> {
    private static int MAX_CACHE_SIZE;

    public Cache(int maxCacheSize) {
        super((int) Math.ceil(maxCacheSize / 0.75) + 1, 0.75f, true);
        this.MAX_CACHE_SIZE = maxCacheSize;
    }

    @Override
    public boolean removeEldestEntry(Map.Entry eldest) {
        return size() > MAX_CACHE_SIZE; // 需要删除最久的元素
    }
}

2、根据LinkedHashMap重新实现

同样,由于LinkedHashMap并非是线程安全的,我们可以仅利用LinkedHashMap的特性自己实现一个。
和FIFO的实现基本一致,只需要将accessOrder = false

public class LruCache<K, V> {
    private static int MAX_CACHE_SIZE = 0;
    private final float LOAD_FACTORY = 0.75f;

    Map<K, V> map;

    public LruCache(int maxCacheSize) {
        this.MAX_CACHE_SIZE = maxCacheSize;
        // 根据 cacheSize 和 填充因子计算cache的容量
        int capacity = (int) Math.ceil(MAX_CACHE_SIZE / LOAD_FACTORY) + 1;
        map = new LinkedHashMap<K, V>(capacity, LOAD_FACTORY, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry eldest) {
                return size() > MAX_CACHE_SIZE;
            }
        };
    }

    public synchronized void put(K key, V value) {
        map.put(key, value);
    }

    public synchronized V get(K key) {
        return map.get(key);
    }

    public synchronized void remove(K key) {
        map.remove(key);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<K, V> entry : map.entrySet()) {
            sb.append(entry.getKey()).append("=")
                    .append(entry.getValue()).append(" ");
        }
        return sb.toString();
    }
}
上一篇下一篇

猜你喜欢

热点阅读