• LRUCache原理分析


    一.注释

    LRUCache的原理,基本都在注释里面描述清楚了。

    /**
     * A cache that holds strong references to a limited number of values. Each time
     * a value is accessed, it is moved to the head of a queue. When a value is
     * added to a full cache, the value at the end of that queue is evicted and may
     * become eligible for garbage collection.
     *
     * <p>If your cached values hold resources that need to be explicitly released,
     * override {@link #entryRemoved}.
     *
     * <p>If a cache miss should be computed on demand for the corresponding keys,
     * override {@link #create}. This simplifies the calling code, allowing it to
     * assume a value will always be returned, even when there's a cache miss.
     *
     * <p>By default, the cache size is measured in the number of entries. Override
     * {@link #sizeOf} to size the cache in different units. For example, this cache
     * is limited to 4MiB of bitmaps:
     * <pre>   {@code
     *   int cacheSize = 4 * 1024 * 1024; // 4MiB
     *   LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
     *       protected int sizeOf(String key, Bitmap value) {
     *           return value.getByteCount();
     *       }
     *   }}</pre>
     *
     * <p>This class is thread-safe. Perform multiple cache operations atomically by
     * synchronizing on the cache: <pre>   {@code
     *   synchronized (cache) {
     *     if (cache.get(key) == null) {
     *         cache.put(key, value);
     *     }
     *   }}</pre>
     *
     * <p>This class does not allow null to be used as a key or value. A return
     * value of null from {@link #get}, {@link #put} or {@link #remove} is
     * unambiguous: the key was not in the cache.
     *
     * <p>This class appeared in Android 3.1 (Honeycomb MR1); it's available as part
     * of <a href="http://developer.android.com/sdk/compatibility-library.html">Android's
     * Support Package</a> for earlier releases.
     */
    View Code

    1.每次一个元素被访问,它会被move到队列的head位置。当某个元素加入到已经full的池里面,最久未使用的一个元素会被delete。

    2.如果元素需要做特殊的释放操作,请重载entryRemoved

    3.如果某个key值,没有对应的初始值,可以通过create方法提供默认值。

    4.创建一个LRUCache需要复写sizeof方法。

    5.这个类是线程安全的。

    二:源代码分析:

    1.变量

    private final LinkedHashMap<K, V> map;
    
        /** Size of this cache in units. Not necessarily the number of elements. */
        private int size;      //count
        private int maxSize;  //容量
    
        private int putCount;  
        private int createCount;
        private int evictionCount;  //delete count
        private int hitCount;     //命中 count
        private int missCount;    //未命中 count  

    2.构造函数

    /**
         * @param maxSize for caches that do not override {@link #sizeOf}, this is
         *     the maximum number of entries in the cache. For all other caches,
         *     this is the maximum sum of the sizes of the entries in this cache.
         */
        public LruCache(int maxSize) {
            if (maxSize <= 0) {
                throw new IllegalArgumentException("maxSize <= 0");
            }
            this.maxSize = maxSize;
            this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
        }

    这段代码最关键的是,new了一个LinkedHashMap,这个hashmap是可以根据访问先后次序,来控制顺序的。

    LinkedHashMap:第一个参数表示初始数据是0个。

    0.75f 是经典的加载因子的数值。

    第三个参数true表示,以访问的先后顺序来排序。也就是做到了,容量满了以后,删除最久未访问的数据。

    3.resize

        /**
         * Sets the size of the cache.
         *
         * @param maxSize The new maximum size.
         */
        public void resize(int maxSize) {
            if (maxSize <= 0) {
                throw new IllegalArgumentException("maxSize <= 0");
            }
    
            synchronized (this) {
                this.maxSize = maxSize;
            }
            trimToSize(maxSize);
        }

    maxsize就是容量,也就是说,队列的最大长度,当full的时候,最后一个会被delete。

    resize 就是调用trimToSize

    4.trimToSize

    /**
         * Remove the eldest entries until the total of remaining entries is at or
         * below the requested size.
         *
         * @param maxSize the maximum size of the cache before returning. May be -1
         *            to evict even 0-sized elements.
         */
        public void trimToSize(int maxSize) {
            while (true) {
                K key;
                V value;
                synchronized (this) {
                    if (size < 0 || (map.isEmpty() && size != 0)) {
                        throw new IllegalStateException(getClass().getName()
                                + ".sizeOf() is reporting inconsistent results!");
                    }
    
                    if (size <= maxSize) {
                        break;
                    }
    
                    Map.Entry<K, V> toEvict = map.eldest();
                    if (toEvict == null) {
                        break;
                    }
    
                    key = toEvict.getKey();
                    value = toEvict.getValue();
                    map.remove(key);
                    size -= safeSizeOf(key, value);
                    evictionCount++;
                }
    
                entryRemoved(true, key, value, null);
            }
        }

    最外面的是while true 死循环。 然后开始判读size的值。1)获取最后的一个entry,拿到后,getkey & value。

    2)map remove掉它。3)然后是获取这个entry占用的大小(不一定是1)。size 减去这个值。 4)evictionCount++

    5)entryRemoved 之前说过,就是可以

     5.get

    /**
         * Returns the value for {@code key} if it exists in the cache or can be
         * created by {@code #create}. If a value was returned, it is moved to the
         * head of the queue. This returns null if a value is not cached and cannot
         * be created.
         */
        public final V get(K key) {
            if (key == null) {
                throw new NullPointerException("key == null");
            }
    
            V mapValue;
            synchronized (this) {
                mapValue = map.get(key);
                if (mapValue != null) {
                    hitCount++;
                    return mapValue;
                }
                missCount++;
            }
    
            /*
             * Attempt to create a value. This may take a long time, and the map
             * may be different when create() returns. If a conflicting value was
             * added to the map while create() was working, we leave that value in
             * the map and release the created value.
             */
    
            V createdValue = create(key);
            if (createdValue == null) {
                return null;
            }
    
            synchronized (this) {
                createCount++;
                mapValue = map.put(key, createdValue);
    
                if (mapValue != null) {
                    // There was a conflict so undo that last put
                    /**
         * Returns the value for {@code key} if it exists in the cache or can be
         * created by {@code #create}. If a value was returned, it is moved to the
         * head of the queue. This returns null if a value is not cached and cannot
         * be created.
         */
        public final V get(K key) {
            if (key == null) {
                throw new NullPointerException("key == null");
            }
    
            V mapValue;
            synchronized (this) {
                mapValue = map.get(key);
                if (mapValue != null) {
                    hitCount++;
                    return mapValue;
                }
                missCount++;
            }
    
            /*
             * Attempt to create a value. This may take a long time, and the map
             * may be different when create() returns. If a conflicting value was
             * added to the map while create() was working, we leave that value in
             * the map and release the created value.
             */
    
            V createdValue = create(key);
            if (createdValue == null) {
                return null;
            }
    
            synchronized (this) {
                createCount++;
                mapValue = map.put(key, createdValue);
    
                if (mapValue != null) {
                    // There was a conflict so undo that last put
                    map.put(key, mapValue);
                } else {
                    size += safeSizeOf(key, createdValue);
                }
            }
    
            if (mapValue != null) {
                entryRemoved(false, key, createdValue, mapValue);
                return mapValue;
            } else {
                trimToSize(maxSize);
                return createdValue;
            }
        }map.put(key, mapValue);
                } else {
                    size += safeSizeOf(key, createdValue);
                }
            }
    
            if (mapValue != null) {
                entryRemoved(false, key, createdValue, mapValue);
                return mapValue;
            } else {
                trimToSize(maxSize);
                return createdValue;
            }
        }
    View Code

    1)从map中获取元素,2)没有的话,就创建一个 3)由于存在多线程问题,可能已经好了一个。所以要删除最新创建的这个,或者增加size的值

    4)如果create的值不需要,就释放create的值,or trimToSize

    6.put 

    /**
         * Caches {@code value} for {@code key}. The value is moved to the head of
         * the queue.
         *
         * @return the previous value mapped by {@code key}.
         */
        public final V put(K key, V value) {
            if (key == null || value == null) {
                throw new NullPointerException("key == null || value == null");
            }
    
            V previous;
            synchronized (this) {
                putCount++;
                size += safeSizeOf(key, value);
                previous = map.put(key, value);
                if (previous != null) {
                    size -= safeSizeOf(key, previous);
                }
            }
    
            if (previous != null) {
                entryRemoved(false, key, previous, value);
            }
    
            trimToSize(maxSize);
            return previous;
        }
    View Code

    map 存在一个多线程 create,put的问题。所以在put的时候,可能由其他线程已经存在了oldvalue值。所以根据

    previous = map.put(key, value);

    这个特性,判断previous的值,来确认是否是重复put的问题。这里关键是牵涉到size的大小问题。

    其他方法,注释已经写的很清楚,不难理解。

    总结:

    • LruCache 封装了 LinkedHashMap,提供了 LRU 缓存的功能;
    • LruCache 通过 trimToSize 方法自动删除最近最少访问的键值对;
    • LruCache 不允许空键值;
    • LruCache 线程安全;
    • LruCache 的源码在不同版本中不一样,需要区分
    • 继承 LruCache 时,必须要复写 sizeOf 方法,用于计算每个条目的大小。
  • 相关阅读:
    Ajax调用 打不开新窗口
    杂记3
    todo WebClient学习
    Repeater 嵌套
    动态生成数个gridview Button得到隐藏ID列的值
    SQL 查询间隔时间大于60s的所有数据
    Apache DbUtils
    一步步学Mybatis-告别繁琐的配置之Mybatis配置文件生成工具 (7)
    一步步学Mybatis-怎么样实现动态SQL查询(6)
    一步步学Mybatis-实现简单的分页效果逻辑 (5)
  • 原文地址:https://www.cnblogs.com/deman/p/7682179.html
Copyright © 2020-2023  润新知