美文网首页
LruCache了解一下

LruCache了解一下

作者: link的勇气 | 来源:发表于2019-10-17 18:25 被阅读0次

    一、LruCache是什么

    该类最开始出现在Android 3.1中,是高速缓存,其中包含对有限数量的值的强引用。每次值被访问时,它都会移到队列的开头。将值添加到缓存中后如果缓存满了,该队列末尾的值将被逐出,并且可能会引发垃圾回收。
    默认情况下,缓存大小以条目数衡量。重写 sizeOf以不同的单位调整缓存大小。如下例,此缓存限制存放4MiB的Bitmap:

        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();
           }
        }
    

    二、解析源码

    构造方法:

        /**
         * @param maxSize 对于不覆盖sizeOf的缓存,这是缓存中的最大条目数。
         * 对于所有其他缓存,这是此缓存中条目大小的最大和。
         */
        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);
        }
    
    

    使用了LinkHashMap作为实际缓存的容器,其构造方法的第三个参数是accessOrder,设置为true表示按照访问顺序排序。

    public void resize(int maxSize)
    设置缓存的大小

    LruCache#get
    返回key的值(如果它存在于缓存中或可以由create创建)。如果返回值,则将其移到队列的开头。如果未缓存值并且无法创建,则返回null。

        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++;
            }
    
            /*
             * 尝试创建一个值。这可能会花费很长时间,并且当create()返回时,map可能会有所不同。
             * 如果在create()工作时将冲突值添加到map中,则将该值保留在map中并释放创建的值。
             */
    
            V createdValue = create(key);
            if (createdValue == null) {
                return null;
            }
    
            synchronized (this) {
                createCount++;
                mapValue = map.put(key, createdValue);
    
                if (mapValue != null) {
                    // 发生了冲突,因此撤消前面插入的创建的值
                    map.put(key, mapValue);
                } else {
                    size += safeSizeOf(key, createdValue);
                }
            }
    
            if (mapValue != null) {
                entryRemoved(false, key, createdValue, mapValue);
                return mapValue;
            } else {
                trimToSize(maxSize);
                return createdValue;
            }
        }
    

    LruCache#put
    为key缓存value。该值将插入到队列的开头。

        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;
        }
    

    LruCache#trimToSize
    删除最旧的条目,直到剩余条目总数等于或小于请求的大小。

        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;
                    }
    
                    //调用了LinkedHashMap#eldest来获取最近最少使用的条目
                    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);
            }
        }
    

    public final V remove(K key)
    删除key条目(如果存在)

    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
    当有条目被移除或调用put导致值被替换时会调用该方法,默认是空实现
    evicted参数为true表示是缓存满了为了腾出空间而移除的,其它情况为false

    protected V create(K key)
    当key未命中缓存时调用该方法生成对应key的值,默认返回null

    protected int sizeOf(K key, V value)
    以用户定义的单位返回key和value的条目大小。默认实现返回1,因此size是条目数,max size是最大条目数。

    public final void evictAll()
    清除缓存,对每个已删除条目调用entryRemoved

    三、LinkedHashMap

    最近最少使用的顺序是由LinkedHashMap来确保的,使用了一个双向链表

        /**
         * 双向链表的头(最老)
         */
        transient LinkedHashMapEntry<K,V> head;
    
        /**
         * 双链表的末尾(最小)
         */
        transient LinkedHashMapEntry<K,V> tail;
    
        /**
         * 此LinkedHashMap的迭代排序方法:true 表示访问顺序, false 表示插入顺序。
         * @serial
         */
        final boolean accessOrder;
    

    前面提到LruCache初始化LinkedHashMap时accessOrder为true,按访问顺序排序。
    accessOrder为true的情况下,双向链表的头是最近最少使用的条目,尾是最近最多使用的条目。

    我们看看这个accessOrder在哪里起到了作用

        public V get(Object key) {
            Node<K,V> e;
            if ((e = getNode(hash(key), key)) == null)
                return null;
            if (accessOrder)
                afterNodeAccess(e);
            return e.value;
        }
    

    在LinkedHashMap#get方法中,accessOrder为true就调用LinkedHashMap#afterNodeAccess

        void afterNodeAccess(Node<K,V> e) { // 将节点移到最后
            LinkedHashMapEntry<K,V> last;
            if (accessOrder && (last = tail) != e) {
                LinkedHashMapEntry<K,V> p =
                    (LinkedHashMapEntry<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;
            }
        }
    

    在put的过程中,如果HashMap中不存在对应键的条目,就用了newNode来生成新的条目,LinkedHashMap重写了该方法,把新条目连接到链表末尾

        Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
            LinkedHashMapEntry<K,V> p =
                new LinkedHashMapEntry<K,V>(hash, key, value, e);
            linkNodeLast(p);
            return p;
        }
    
        // 连接到链表末尾
        private void linkNodeLast(LinkedHashMapEntry<K,V> p) {
            LinkedHashMapEntry<K,V> last = tail;
            tail = p;
            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
        }
    

    前面提到移除条目时调用LinkedHashMap#eldest获取最近最少使用条目,该方法直接返回了head

        public Map.Entry<K, V> eldest() {
            return head;
        }
    

    四、实现一个简易LruCache

    import java.util.LinkedHashMap;
    import java.util.Map;
    
    public class LruCache<K,V> extends LinkedHashMap<K,V>{
        int cacheSize;
        public LruCache(int cacheSize){
            super((int) Math.ceil(cacheSize / 0.75),0.75f,true);
            this.cacheSize = cacheSize;
        }
    
        @Override
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
            return size()>cacheSize;
        }
    }
    
    1. 继承了LinkedHashMap,调用父类构造方法,设置双向链表按访问顺序排序。

    2. 重写 LinkedHashMap#removeEldestEntry 方法,当 size 大于指定缓存容量时,就返回了true,LinkedHashMap就会删掉最旧的元素。

    相关文章

      网友评论

          本文标题:LruCache了解一下

          本文链接:https://www.haomeiwen.com/subject/fsyumctx.html