• 用LindedHashMap实现LRU算法


      LRU,最近最少使用。如果我们用一个数据结构来实现LRU的话,那么需要满足两个条件,第一个该数据结构需要存储最近使用或未使用的,第二个,需要限制这个数据结构的大小。

      我们用LindedHashMap实现LRU,第一需要设置accessOrder,在默认情况下,accessOrder是为false,表示顺序为插入顺序。为true时,表示会根据访问顺序排序(在get时),最新使用的排在尾巴上。初始化设置accessOrder的源码如下:

        /**
         * Constructs an empty <tt>LinkedHashMap</tt> instance with the
         * specified initial capacity, load factor and ordering mode.
         *
         * @param  initialCapacity the initial capacity
         * @param  loadFactor      the load factor
         * @param  accessOrder     the ordering mode - <tt>true</tt> for
         *         access-order, <tt>false</tt> for insertion-order
         * @throws IllegalArgumentException if the initial capacity is negative
         *         or the load factor is nonpositive
         */
        public LinkedHashMap(int initialCapacity,
                             float loadFactor,
                             boolean accessOrder) {
            super(initialCapacity, loadFactor);
            this.accessOrder = accessOrder;
        }

      在get或者方法里都会调用afterNodeAccess,这个方法就是把当前node放在尾巴上(看不明白没关系,看到这个注释了吗,// move node to last,hhhhhh)

        void afterNodeAccess(Node<K,V> e) { // move node to last
            LinkedHashMap.Entry<K,V> last;
            if (accessOrder && (last = tail) != e) {
                LinkedHashMap.Entry<K,V> p =
                    (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
                p.after = null;
                if (b == null)
                    head = a;
                else
                    b.after = a;
                if (a != null)
                    a.before = b;
                else
                    last = b;
                if (last == null)
                    head = p;
                else {
                    p.before = last;
                    last.after = p;
                }
                tail = p;
                ++modCount;
            }
        }

      这样,设置了accessOrder=true,我们就满足了第一个条件,此时LinkedHashMap的顺序是访问顺序,最新使用的在后面。然后我们需要重写removeEldestEntry这个方法,这个方面默认是返回false,表示不需要移除第一个,需要重写它,表示需要移除第一个,即最近未使用的。在源码里也有说明:

    /**
         *.........
         * <p>Sample use: this override will allow the map to grow up to 100
         * entries and then delete the eldest entry each time a new entry is
         * added, maintaining a steady state of 100 entries.
         * <pre>
         *     private static final int MAX_ENTRIES = 100;
         *
         *     protected boolean removeEldestEntry(Map.Entry eldest) {
         *        return size() &gt; MAX_ENTRIES;
         *     }
         * </pre>
         *
         *...........
         */
        protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
            return false;
        }

      所以我们只需要这样重写,当Map的最大值大于设定的最大值时,需要移除第一个:

      LinkedHashMap<Integer, Integer> map = new LinkedHashMap<Integer,Integer>(maxSize, 0.75f, true){
                @Override
                protected boolean removeEldestEntry(Map.Entry<Integer,Integer> eldest) {
                    return this.size() > maxSize;
                }
            };

      这样重写就好啦,当我们每次使用LinkedHashMap里的元素时,使用的这个元素就会放在最后面,最长未使用的就会在前面。当新加的元素超过Map的大小时,就会移除第一个,然后把新加的放在最后面。

    附上测试代码和结果;

    /**
     * main
     *
     * @description 测试
     * @author zhui
     * @date 2020-12-23 14:09
     * @version v1.0.0
     */
    public class main {
        private static ExecutorService executorService;
        public static void main(String[] args) throws InterruptedException{
            createThreadPool();
            LRUByLinkedHashMap(3);
        }
    
        /**
         * @description 创建一个单线程,用来测试
         * @return void
         **/
        public static void createThreadPool(){
            executorService = Executors.newSingleThreadExecutor();
        }
    
        /**
         * @description 测试代码
         * @return void
         **/
        public static void LRUByLinkedHashMap(int maxSize) throws InterruptedException{
            // LRU的LinkedHashMap
            LinkedHashMap<Integer, Integer> map = new LinkedHashMap<Integer,Integer>(maxSize, 0.75f, true){
                @Override
                protected boolean removeEldestEntry(Map.Entry<Integer,Integer> eldest) {
                    return this.size() > maxSize;
                }
            };
            for (int i = 1; i <= maxSize; i++) {
                map.put(i, 0);
            }
            System.out.println("初始map:" +map);
    
            // 测试
            for (int i = 1; i <= 10; i++){
                int temp = randInt(1,100);
                if (temp % 2 == 0) {
                    // 对map的key随机get
                    randomGetValue(maxSize, map);
                }else {
                    // 对map进行随机put
                    randomPutValue(maxSize,map);
                }
            }
            // 关闭单线程
            executorService.shutdown();
        }
        /**
         * @description 对map随机取值,观察其顺序变化
         * @param maxSize map大小,作为key取值范围
         * @param map map
         **/
        public static void randomGetValue(int maxSize, Map<Integer, Integer> map) throws InterruptedException{
            final CountDownLatch end = new CountDownLatch(maxSize);
            for(int i=1; i<= maxSize; i++){
                executorService.execute(() -> {
                    int key =  randInt(1, maxSize);
                    forGetValueByRandom(key, map);
                    System.out.println("key=" + key + ",随机取值后map:" + map);
                    end.countDown();
                });
            }
            end.await();
        }
    
        /**
         * @description 给map新put 1个键值对,观察剩下的map变化
         * @param maxSize map的size大小,决定新键值对的下限
         * @param map map
         * @return void
         **/
        public static void randomPutValue(int maxSize, Map<Integer, Integer> map){
            executorService.execute(() -> {
                int key = randInt(maxSize+1, 10);
                forGetValueByRandom(key, map);
                System.out.println("插入" + key + "后,map:" + map);
            });
        }
    
        /**
         * @description 根据key,随机对这个key进行get1次,没有则添加一个键值对,同时更新其value
         * @param key key
         * @param map map
         * @return void
         **/
        public static void forGetValueByRandom(int key, Map<Integer, Integer> map){
            Integer num = map.getOrDefault(key, 0);
            map.put(key, num + 1);
        }
    
        /**
         * @description 在一个范围内随机取值
         * @param min 随机取值最小值
         * @param max 随机取值最大值
         * @return int
         **/
        public static int randInt(int min, int max) {
            Random rand = new Random();
            int randomNum = rand.nextInt((max - min) + 1) + min;
            return randomNum;
        }
    }

      然后我们看些结果,是否和预期一样呢?可以看到新加的元素或者刚使用的元素,都会在最后面,简直完美!

    初始map:{1=0, 2=0, 3=0}
    插入9后,map:{2=0, 3=0, 9=1}            //1被移除了
    key=3,随机取值后map:{2=0, 9=1, 3=1}     //3放到最后面
    key=1,随机取值后map:{9=1, 3=1, 1=1}     //2被移除,1在最后面
    key=3,随机取值后map:{9=1, 1=1, 3=2}     //3在最后面
    插入8后,map:{1=1, 3=2, 8=1}            //8在最后面
    插入7后,map:{3=2, 8=1, 7=1}            //7在最后面
    key=3,随机取值后map:{8=1, 7=1, 3=3}     //3在最后面
    key=3,随机取值后map:{8=1, 7=1, 3=4}     //3在最后面
    key=2,随机取值后map:{7=1, 3=4, 2=1}     //2在最后面
    key=1,随机取值后map:{3=4, 2=1, 1=1}     //1在最后面
    key=1,随机取值后map:{3=4, 2=1, 1=2}     //1在最后面
    key=2,随机取值后map:{3=4, 1=2, 2=2}     //2在最后面
    key=2,随机取值后map:{3=4, 1=2, 2=3}     //2在最后面
    key=1,随机取值后map:{3=4, 2=3, 1=3}     //1在最后面    
    key=1,随机取值后map:{3=4, 2=3, 1=4}     //1在最后面
    插入8后,map:{2=3, 1=4, 8=1}            //8在最后面
    插入4后,map:{1=4, 8=1, 4=1}            //4在最后面
    插入6后,map:{8=1, 4=1, 6=1}            //6在最后面
  • 相关阅读:
    (转)使用vsphere client 克隆虚拟机
    【转】VIM高级用法笔记
    Oracle RAC的Failover
    /dev/shm过小导致ORA00845错误解决方法
    (转)How to use udev for Oracle ASM in Oracle Linux 6
    ORACLE十进制与十六进制的转换
    解决Oracle RAC不能自动启动的问题
    RAC集群时间同步服务
    db link hang的解决方法
    【转载】Oracle数据恢复 Linux / Unix 误删除的文件恢复
  • 原文地址:https://www.cnblogs.com/zhuii/p/14179734.html
Copyright © 2020-2023  润新知