美文网首页
LruCache源码浅析

LruCache源码浅析

作者: leenpong | 来源:发表于2019-03-25 20:01 被阅读0次

    前言:自从Andorid3.1之后,谷歌引入了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
     *
     * <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.
     *
    
    • 官方文档指出几点:
      --hold strong reference: 该缓存引用的是强引用。
      --a value is accessed, it is moved to the head of a queue :每次一个数据被访问,就会被移到对头
      --要重载sizeof用于测量当前内存大小
      --不支持空的key。

    LRU:什么是LRU算法? LRU是Least Recently Used的缩写,即最近最少使用
    LruCache里面最重要的是维护了一个双向链表(该链表支持有序插入)用于存放缓存数据,LinkedHashMap底层正是实现了LRU算法.

        private final LinkedHashMap<K, V> map;
    

    LinkedHashMap :

    • LinkedHashMap is an implementation of {@link Map} that guarantees > iteration order.
    • All optional operations are supported.
    • <p>All elements are permitted as keys or values, including null.
    • <p>Entries are kept in a doubly-linked list. The iteration order is, by > > > default, theorder in which keys were inserted. Reinserting an already-present key > doesn't change theorder. If the three argument constructor is used, and {@code accessOrder} is specified as
      {@code true}, the iteration will be in the order that entries were accessed.
    • The access order is affected by {@code put}, {@code get}, and {@code putAll} operations,
    • but not by operations on the collection views.

    LinkedHashMap 继承HashMap,里面维护一个内部类LinkeEntry双向链表用于存储数据。
    具体分析可以看该博客:图解LinkedHashMap原理


    我们先开LruCache的构造函数

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

    构造了一个LinkedHashMap,而且第三个参数为true,表示访问有序(这个是实现该LruCache算法的核心。)

    put函数:

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

    分析:
    每次放入一个数据,size会根据当前的数据大小增加

        private int safeSizeOf(K key, V value) {
            int result = sizeOf(key, value);//该方法就是上面说的要重写的方法
            if (result < 0) {
                throw new IllegalStateException("Negative size: " + key + "=" + value);
            }
            return result;
        }
    

    然后将数据放入map,返回一个previous,其中,map.put(key, value):
    HashMap.java

        @Override public V put(K key, V value) {
            if (key == null) {
                return putValueForNullKey(value);
            }
    
            int hash = Collections.secondaryHash(key);
            HashMapEntry<K, V>[] tab = table;
            int index = hash & (tab.length - 1);
            for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
                if (e.hash == hash && key.equals(e.key)) {
                    preModify(e);
                    V oldValue = e.value;
                    e.value = value;
                    return oldValue;
                }
            }
    
            // No entry for (non-null) key is present; create one
            modCount++;
            if (size++ > threshold) {
                tab = doubleCapacity();
                index = hash & (tab.length - 1);
            }
            addNewEntry(key, value, hash, index);
            return null;
        }
    

    当当前的key在map里面是有的时候,返回旧的数据,并且被回收掉。

    if (previous != null) {
                    size -= safeSizeOf(key, previous);
                }
            }
    
            if (previous != null) {
                entryRemoved(false, key, previous, value);
            }
    
    
    public void trimToSize(int maxSize) {
            while (true) {
                K key;
                V value;
                synchronized (this) {
                    //如果重载的sizeof方法返回的大小单位跟max传入的大小单位不同,这里会报错
                    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);
            }
    
    

    对缓存进行内存裁剪,不断第将队尾的数据删除,直到size<=maxSize.

    其中最关键的就是map.eldest()这段代码,拿到当前最老的数据,也就是说最近最少访问的数据。

        public Entry<K, V> eldest() {
            LinkedEntry<K, V> eldest = header.nxt;
            return eldest != header ? eldest : null;
        }
    

    由于在构造LinkedMap的时候,构造参数accessOrder是true,也就是说当前的链表的顺序是根据访问时间去定当前数据的位置,越久没访问,越前。

    LinkedHashMap:
        /**
         * Relinks the given entry to the tail of the list. Under access ordering,
         * this method is invoked whenever the value of a  pre-existing entry is
         * read by Map.get or modified by Map.put.
         */
        private void makeTail(LinkedEntry<K, V> e) {
            // Unlink e
            e.prv.nxt = e.nxt;
            e.nxt.prv = e.prv;
    
            // Relink e as tail
            LinkedEntry<K, V> header = this.header;
            LinkedEntry<K, V> oldTail = header.prv;
            e.nxt = header;
            e.prv = oldTail;
            oldTail.nxt = header.prv = e;
            modCount++;
        }
    

    上面就是每次调整数据顺序的代码。

    --在如果做图片处理的时候,可以复写entryRemoved对从map里面移除的图片进行复用内存。

    get方法:

    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;
            }
    
          //下面代码跟put的代码差不多,也是新建一个value放入map
            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里面没有对应的key的数据或者出于其他原因key对应的数据被删除了,提供了create(key)方法给用于去重新创建对应的数据。

    LruCache

    相关文章

      网友评论

          本文标题:LruCache源码浅析

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