源码全面解析---LruCache

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_25859403/article/details/52145819

一、前面要说的

LruCache是Android提供的一种缓存算法,Lru就是Last Recently Use。
因此,LruCache就是通过条目的访问状况对条目进行排序,然后将最近访问(写入或者读取)的条目移动到队列头部(准确地应该是链表头部,下同),将最近最少使用的条目移动到队列尾部,然后在缓存被填满或者收到移除队尾的条目。

下面是源码 的分析,不过在看源码分析之前先了解一下几个名词:

  • cache hit: 缓存命中。在LruCache中,如果根据key值能够在map中找到相应的不为null的value, 那么,就为cache hit。

  • cache miss:缓存未命中。和上面相反。

  • eviction:驱逐 。所谓的驱逐可以理解为由于size超过了规定的maxSize而自动移除。

  • 条目:entry

二、源码分析


package android.support.v4.util;

import java.util.LinkedHashMap;
import java.util.Map;

public class LruCache<K, V> {
    /**
     * 问题:为什么采用LinkedHashMap来作为缓存的容器?
     */
    private final LinkedHashMap<K, V> map;

    /** Size of this cache in units. Not necessarily the number of elements. */
    private int size;// 当前缓存内容的大小
    private int maxSize;// 最大缓存容量

    private int putCount;// map的put方法被调用的次数,也可理解为发生条目置换的次数 。
    private int createCount;// create方法被调用的次数
    private int evictionCount;// 驱逐条目的次数(可参考trimToSize(...))
    private int hitCount;// 缓存命中的次数
    private int missCount;// 缓存未命中的次数

    /**
     * @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;
        /*
         * 初始化一个LinkedHashMap,这个LinkedHashMap的
         * 
         * 初始容量--0 ,
         * 
         * 加载因子--0.75f ,
         * 
         * 排序模式--插入顺序
         */
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }

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

    /**
     * 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.
     * 
     * 译:根据key返回相应的value,如果这个value存在于缓存中或这个value可以通过create()方法创建出来,否则会返回null。
     * 这个value被返回后,就会被移到队列的头部.
     * 
     * 从代码中我们可以看出这个方法可能返回三种情况的值: 1.mapValue : 当
     * 通过get(key)方法取出的value不为null的时候,就会返回mapValue,他就是取出来的value。
     * 
     * 2.createdValue:当上面的mapValue为null,也就是说本来map中就不存在对应key值的value时,
     * 就会调用create(key)方法创建一个value。然而,这并不够,默认情况下create(key)的返回值为null。
     * 因此,如果想要返回这个createdValue就要重写create(key)方法,让create(key)返回一个不为null的值。
     * 3.null:如果以上两者都为null,则返回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++;
        }

        /*
         */

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

    /**
     * Caches {@code value} for {@code key}. The value is moved to the head of
     * the queue.
     *
     * @return the previous value mapped by {@code key}.
     * 
     *         此方法用于将条目添加到缓存。先添加的value将会被移动到队列头部。
     * 
     *         返回 被置换出来的旧value。
     */
    public final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        V previous; // 用于存放替换出来的value。
        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;
    }

    /**
     * Remove the eldest entries until the total of remaining entries is at or
     * below the requested size. 译:移除最老的条目,直到剩余条目的总数低于maxSize
     * 
     * @param maxSize
     *            the maximum size of the cache before returning. May be -1 to
     *            evict even 0-sized elements.
     * 
     *            方法解释: 整个trimToSize(...)中仅仅包含一个无限循环,
     *                   不断移除多余的旧条目,以保证size不超过maxSize。
     */
    public void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                // 如果当前已使用的缓存<0或者(map没有条目且当前已使用的缓存不为0)
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    // 抛出一个IllegalStateException
                    throw new IllegalStateException(
                            getClass().getName() + ".sizeOf() is reporting inconsistent results!");
                }

                // 如果已使用缓存数没有超过maxSize或者当前map中没有条目,那么师妹也不做,直接跳出本次循环
                if (size <= maxSize || map.isEmpty()) {
                    break;
                }

                /*
                 * 当size>maxSize的时候,会执行下面的代码。
                 */
                // 取出map中最老的元素,即链表头上的元素。
                Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key); // 移除最老的元素
                size -= safeSizeOf(key, value);// --> size-=1(默认情况);
                evictionCount++;// 驱逐条目的次数
            }

            entryRemoved(true, key, value, null);
        }
    }

    /**
     * Removes the entry for {@code key} if it exists.
     *
     * @return the previous value mapped by {@code key}.
     * 
     * 此方法用于移除一个缓存中存在的条目
     * 
     *  返回被remove掉的条目的value。
     * 
     */
    public final V remove(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V previous;
        synchronized (this) {
            previous = map.remove(key);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, null);
        }

        return previous;
    }

    /**
     * Called for entries that have been evicted or removed. This method is
     * invoked when a value is evicted to make space, removed by a call to
     * {@link #remove}, or replaced by a call to {@link #put}. The default
     * implementation does nothing.
     * 
     * 当一个value被驱逐来释放空间,调用remove来移除或者调用put方法替换的时候会调用这个方法。 默认实现什么都不做。
     * 
     * 
     * @param evicted
     *            true 表示给条目由于需要是释放空间而被移除(即是被驱逐的)--非正常的。 false表示条目是被通过remove(K
     *            key)移除的,或者put(K key, V value)置换掉--人为操作的。
     * 
     * @param newValue
     *            用于置换oldValue的value。因此,如果它不为null,这个entryRemoved()一定 是在put(K
     *            key, V value)中被调用的。
     *            如果newValue为null,说明entryRemoved()是在trimToSize(int
     *            maxSize)或者remove(K key)中调用的。
     * 
     */
    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {
    }

    /**
     * Called after a cache miss to compute a value for the corresponding key.
     * Returns the computed value or null if no value can be computed. The
     * default implementation returns null.
     *
     * <p>
     * The method is called without synchronization: other threads may access
     * the cache while this method is executing.
     *
     * <p>
     * If a value for {@code key} exists in the cache when this method returns,
     * the created value will be released with {@link #entryRemoved} and
     * discarded. This can occur when multiple threads request the same key at
     * the same time (causing multiple values to be created), or when one thread
     * calls {@link #put} while another is creating a value for the same key.
     * 
     * 译:1.当cache miss 后,会根据相应的key值计算出一个新的value(下称为 createValue)。
     * 此方法返回createValue或者null(value不能计算)。此方法默认返回null。 
     * 
     * 2. 这个方法是不同步的。
     *  
     * 3.如果返回的createValue缓存中已经存在,那么这个createValue会被entryRemoved释放掉({@link #get(...) 132行})。
     *           这里提示我们,有时候需要重写entryRemoved()方法来释放value。
     * 
     * 
     */
    protected V create(K key) {
        return null;
    }

    /**
     * 此方法主要用于判断({@link #sizeOf(Object, Object)})返回值的正确性。
     * 
     * 如果sizeOf()的返回值正常,则返回sizeOf()的返回值。否则抛出一个IllegalStateException。

     */
    private int safeSizeOf(K key, V value) {
        int result = sizeOf(key, value);
        if (result < 0) {
            throw new IllegalStateException("Negative size: " + key + "=" + value);
        }
        return result;
    }

    /**
     * Returns the size of the entry for {@code key} and {@code value} in
     * user-defined units. The default implementation returns 1 so that size is
     * the number of entries and max size is the maximum number of entries.
     *
     * <p>
     * An entry's size must not change while it is in the cache.
     * 
     * 返回指定的条目的size,单位是用户自定义。 默认实现是返回1,这时候size表示entris的数量, max
     * size表示entries的最大数量。entry的大小在缓存期间不能改变 一般情况下我们需要重写sizeOf方法按不同的单位来计算缓存。
     * 
     */
    protected int sizeOf(K key, V value) {
        return 1;
    }

    /**
     * Clear the cache, calling {@link #entryRemoved} on each removed entry.
     * 
     * 此方法用于清除缓存,通过将maxSize设定为-1.
     * 
     * 
     */
    public final void evictAll() {
        trimToSize(-1); // -1 will evict 0-sized elements
    }

    public synchronized final int size() {
        return size;
    }

    public synchronized final int maxSize() {
        return maxSize;
    }

    public synchronized final int hitCount() {
        return hitCount;
    }

    public synchronized final int missCount() {
        return missCount;
    }

    public synchronized final int createCount() {
        return createCount;
    }

    public synchronized final int putCount() {
        return putCount;
    }

    public synchronized final int evictionCount() {
        return evictionCount;
    }

    public synchronized final Map<K, V> snapshot() {
        return new LinkedHashMap<K, V>(map);
    }

    @Override
    public synchronized final String toString() {
        int accesses = hitCount + missCount;
        int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
        return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", maxSize, hitCount, missCount,
                hitPercent);
    }
}

三、 最后的问题

(一) 为什么采用LinkedHashMap来作为缓存的容器?

原因很简单,我们知道LinkedHashMap的实现采用了哈希表和双重链表。因此这使得LinkedHashMap具有两个特点:
1)唯一性
当然这里的唯一性是指key的唯一性,唯一性是由哈希表来保证的。
2)有序性 这里的有序仅仅代表 插入和读取的顺序一致,而不是在内部进行排序。有序性是由双重链表决定的。
这里重点说一下有序性。

public LinkedHashMap(int initialCapacity,//初始容量
                     float loadFactor,//加载因子
                     boolean accessOrder)//访问顺序

当accessOrder为true时表示按访问顺序排序;
当accessOrder为false时表示按插入顺序排序。
所谓的访问顺序中的就是将最近访问的元素移动到链表的尾部。

猜你喜欢

转载自blog.csdn.net/qq_25859403/article/details/52145819