Android LruCache源码简析

LruCache源码解析

缓存置换算法:LRU,LFU,FIFO

LRU:淘汰最长时间未使用的对象
LFU:淘汰一段时间内使用最少的对象,也就是淘汰使用频率最低的对象 
FIFO:frist in first out,先进先出

源码解析

LruCache是线程安全的,对maxSize、map的操作,都有用synchronized

package android.support.v4.util;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;

public class LruCache<K, V> {
    private final LinkedHashMap<K, V> map;//双向循环链表,存放缓存对象
    private int size;//当前缓存内容的大小。它不一定是元素的个数,比如如果缓存的是图片,一般用的是图片占用的内存大小
    private int maxSize;//最大可缓存的大小
    private int putCount;// put 方法被调用的次数
    private int createCount;// create(Object) 被调用的次数
    private int evictionCount;// 被置换出来的元素的个数
    private int hitCount;// get 方法命中缓存中的元素的次数
    private int missCount;// get 方法未命中缓存中元素的次数

   /** 
    * @param maxSize 最大可缓存大小  
    */
    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);
    }

    /**
     * 重置最大可缓存大小
     * @param maxSize 最大可缓存大小
     */
    public void resize(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        synchronized (this) {
            this.maxSize = maxSize;
        }
        trimToSize(maxSize);
    }

    /**
     * 1、根据key获取缓存value,并把该元素移动到链表的head节点
     * 2、如果没有找到对应value,那么通过create创建value并添加到map,
     * 3、如果添加时有了value(create方法非线程安全),那么保留已经存在的value,抛弃create的value
     * 4、如果添加时还没value,那么就包create的value添加到map中缓存
     */
    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++;//get 方法命中次数递增
                return mapValue;
            }
            missCount++;//没有找到缓存,get 方法未命中次数递增
        }
        //到这里,说明没有找到缓存
        V createdValue = create(key);//创建value
        if (createdValue == null) {//创建value失败,返回null,结束
            return null;
        }
        synchronized (this) {
            createCount++;//create方法成功创建value的次数
            /* 把createValue添加到缓存中,并返回旧的缓存value
             * 由于create方法是非线程安全的,所以在create过程中,有可能其他线程更快的create了一个value并成功添加到map了
             */
            mapValue = map.put(key, createdValue);
            if (mapValue != null) {//这表示有旧的缓存,跟新的缓存有冲突
                map.put(key, mapValue);//保留旧的缓存,丢弃createdValue
            } else {//没有旧缓存,那就只要把createValue添加进去就好了,size递增
                size += safeSizeOf(key, createdValue);//sizeOf方法可以重写,默认是数量,如果是内存大小,要自己实现
            }
        }
        if (mapValue != null) {//已经有旧缓存了
            //通知之前创建的值已经被移除,而改为mapValue
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {//没有冲突时,因为放入了新创建的值,大小已经有变化,所以需要修整大小
            trimToSize(maxSize);
            return createdValue;
        }
    }

    /**
     * 把value根据key添加到map中,并把value移动到链表的head节点
     * @return 如果添加value之前,该key已经存在value,那么新的value添加到map后,返回旧的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);
            //value根据key添加到map中,如果key本来存在previousValue,那么添加了value后把原来的previousValue返回
            previous = map.put(key, value);
            if (previous != null) {//旧的value被置换,需要减去对应的size
                size -= safeSizeOf(key, previous);
            }
        }
        if (previous != null) {//通知之前创建的值已经被移除,而改为mapValue
            entryRemoved(false, key, previous, value);
        }
        //没有冲突时,因为放入了新创建的值,大小已经有变化,所以需要修整大小
        trimToSize(maxSize);
        return previous;
    }

    /**
     * 放入新的缓存对象时,缓存大小发生变化时,需要调用该方法,保证缓存不会超过maxSize
     * 根据传进来的maxSize,如果当前大小超过了这个maxSize,则会移除最老的结点,直到不超过
     * @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 || map.isEmpty()) {
                    break;
                }
                /* 遍历map,当前指向的是head节点,next()指向head的下一个节点                 * LinkedHashMap是双向循环链表,head.next()就是最后一个元素,
                 * 每次最近访问的对象都会被移到head节点,所以末节点(head的下一个节点)一定是最久没被访问过的节点了
                 */
                Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);//移除最老的缓存对象
                size -= safeSizeOf(key, value);//移除一个缓存对象,缓存size响应减少
                evictionCount++;//移除一个缓存对象,置换数递增
            }
            entryRemoved(true, key, value, null);//通知之前创建的值已经被移除,而改为mapValue
        }
    }

    /**
     * 删除key对应的缓存对象
     * @return the previous value mapped by {@code key}.
     */
    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更新
                size -= safeSizeOf(key, previous);
            }
        }
        if (previous != null) {//通知之前创建的值已经被移除,而改为mapValue
            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.
     *
     * <p>The method is called without synchronization: other threads may
     * access the cache while this method is executing.
     *
     * @param evicted true表示为了保持内存不超过限制而移除缓存对象,false表示由put、remove方法触发的
     * @param newValue 
     */
    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.
     */
    protected V create(K key) {
        return null;
    }

    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.
     */
    protected int sizeOf(K key, V value) {
        return 1;
    }

    /**
     * Clear the cache, calling {@link #entryRemoved} on each removed entry.
     */
    public final void evictAll() {
        trimToSize(-1); // -1 will evict 0-sized elements
    }

    /**
     * For caches that do not override {@link #sizeOf}, this returns the number
     * of entries in the cache. For all other caches, this returns the sum of
     * the sizes of the entries in this cache.
     */
    public synchronized final int size() {
        return size;
    }

    /**
     * For caches that do not override {@link #sizeOf}, this returns the maximum
     * number of entries in the cache. For all other caches, this returns the
     * maximum sum of the sizes of the entries in this cache.
     */
    public synchronized final int maxSize() {
        return maxSize;
    }

    /**
     * Returns the number of times {@link #get} returned a value that was
     * already present in the cache.
     */
    public synchronized final int hitCount() {
        return hitCount;
    }

    /**
     * Returns the number of times {@link #get} returned null or required a new
     * value to be created.
     */
    public synchronized final int missCount() {
        return missCount;
    }

    /**
     * Returns the number of times {@link #create(Object)} returned a value.
     */
    public synchronized final int createCount() {
        return createCount;
    }

    /**
     * Returns the number of times {@link #put} was called.
     */
    public synchronized final int putCount() {
        return putCount;
    }

    /**
     * Returns the number of values that have been evicted.
     */
    public synchronized final int evictionCount() {
        return evictionCount;
    }

    /**
     * Returns a copy of the current contents of the cache, ordered from least
     * recently accessed to most recently accessed.
     */
    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(Locale.US, "LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
                maxSize, hitCount, missCount, hitPercent);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值