• FIFO与LRU实现(Java)


    一、概述

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

    1、缓存置换算法

    • FIFO:First In First Out,先进先出,和队列保持一致。最先进来的最早出去。
    • LRU:Least Recently Used,最近最少使用。总是淘汰最近没有使用的。其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”
      也就是说,淘汰最近一段时间内最长时间未访问过的数据。根据程序局部性原理,刚被访问的数据,可能马上又要被访问;而较长时间内没有被访问的数据,可能最近不会被访问。
    • LFU:Least Frequently Used,最近使用次数最少。即淘汰使用次数最少的。
    • OPT:Optimal,最佳置换。置换以后永不再被访问,或者在将来最迟才会被访问的。该算法无法实现,通常作为衡量其他算法的标准。

    2、缓存置换算法的要素

    • (1)缓存不是无限大,需要有一个固定的大小来约束其大小
    • (2)缓存满后,再次插入需要替换掉某些元素,才能添加新元素
    • (3)每次访问完缓存,可能需要改变缓存元素的状态,如元素顺序的改变

    3、Java LinkedHashMap简介

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

    • 当参数accessOrder = true时,则按照访问顺序对Map排序,那么调用get()方法后,会将这次访问的元素移至链表尾部。不断访问可以形成按访问顺序排序的链表。
    • 当参数accessOrder = false时,则按照插入顺序对Map排序。先插入的元素放置在链表的首部,按照尾插入的方式维护链表。
    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();
        }
    }


    转载自:https://www.jianshu.com/p/33e572da4b58
  • 相关阅读:
    Android下的多线程
    01背包问题
    用锐捷使你的笔记本成为WIFI基站,让其他电脑还有我们的手机使用无线上网吧
    如何在eclipse的android工程中添加外部javadoc.jar包,方便开发
    umask函数的用处
    支持我一下吧!
    ios越狱内购提示Environment:Sandbox
    plt_System_Security_Cryptography_HMAC_KeySetup_byte___byte
    蛋疼的时候写三消游戏(十二)
    cocos2dx做游戏(搭建环境)
  • 原文地址:https://www.cnblogs.com/hirampeng/p/9756200.html
Copyright © 2020-2023  润新知