LruCache 源码解析

3,995 阅读3分钟

LruCache源码链接

LruCache在网络请求,图片缓存等场景下都有使用,其源码非常短(只有260行),所以我们仔细的通读一遍源码.

成员变量

private final LinkedHashMap map;//内部通过LinkedHashmap实现Lru算法
private int size;//map中元素个数
private int maxSize;//LruCache支持的最大元素个数
private int putCount;
private int createCount;
private int evictionCount;
private int hitCount;
private int missCount;

构造方法

public LruCache(int maxSize) {
    if (maxSize 
        throw new IllegalArgumentException("maxSize 
    }
    this.maxSize = maxSize;//LruCache容量
    this.map = new LinkedHashMap(0, 0.75f, true);
    }

主要看最后一行,初始化map,其中初始容量为0,装载因子为0.75,保持访问顺序。

In addition, a LinkedHashMap can be configured in the constructor to use a least - recently - used (LRU) algorithm based on accesses, so elements that haven’t been accessed (and thus are candidates for removal) appear at the front of the list. —— Thinking in Java

《Thinking in Java》中就有提到,使用LinkedHashMap可以实现LRU算法。

简单看一下LinkedHashMap的API文档:

LinkedHashMap (int initialCapacity, 
                float loadFactor, 
                boolean accessOrder)
                
参数 含义
initialCapacity 初始容量大小
loadFactor 装载因子
accessOrder true:基于访问顺序,false:基于插入顺序

accessOrder == true,保证了Map内部基于访问顺序,按照访问顺序逆序排列。也就是说,最近访问的元素排在Map的最后。当需要删除元素时,将会从头部开始删除。正是这个特性,使得通过LinkedHashMap可以实现LRU算法。

主要方法

  • get(key)
    public synchronized final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }
        V result = map.get(key);
        if (result != null) {//命中缓存
            hitCount++;
            return result;
        }
        missCount++;
        // TODO: release the lock while calling this potentially slow user code
        result = create(key); // create(key)默认返回null
        if (result != null) {//这里使用了double-check,但未加锁(主要为了效率)
            createCount++;
            size += safeSizeOf(key, result);
            map.put(key, result);
            trimToSize(maxSize);//增加了元素后,对map进行修剪,保持size 
        }
        return result;
        }
    

根据key的值,从LinkedHashMap中获取指定的元素,如果Map中没有该key,则返回null

  • put(key, value)
    public synchronized final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }
        putCount++;
        size += safeSizeOf(key, value);
        V previous = map.put(key, value);
        if (previous != null) { //如果map中已经有该key,则恢复size的值
            size -= safeSizeOf(key, previous);
        }
        trimToSize(maxSize);//插入后,判断是否超出maxSize,若超过则进行裁剪
        return previous;
        }
    

插入key-value : map.put(key, value),相同key的value值会被覆盖,保留最后插入的key-value

  • trimToSize(maxSize)
    private void trimToSize(int maxSize) {
        while (size > maxSize && !map.isEmpty()) {
            Map.Entry toEvict = map.entrySet().iterator().next();
            if (toEvict == null) {
                break; // map 为空; 如果size为0,下面会抛出异常
            }
            K key = toEvict.getKey();
            V value = toEvict.getValue();
            map.remove(key);// 从头部删除一个item
            size -= safeSizeOf(key, value);//更新size
            evictionCount++;
            // TODO: release the lock while calling this potentially slow user code
            entryEvicted(key, value); //空方法,未实现
        }
        if (size < 0 || (map.isEmpty() && size != 0)) {
            throw new IllegalStateException(getClass().getName()
                    + ".sizeOf() is reporting inconsistent results!");
        }
        }
    

根据初始化时设置的maxSize对map进行修剪,从头部开始删除元素,直到size <= maxsize<="" code="">结束

  • remove(key)
    public synchronized final V remove(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }
        V previous = map.remove(key);
        if (previous != null) {
            size -= safeSizeOf(key, previous); //size = size -1
        }
        return previous;
        }
    

调用LinkedHashMap的remove(key)方法,并修改size大小

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

对sizeOf(key, value)进行了一次封装,恒返回1

  • sizeOf(key, value)
    protected int sizeOf(K key, V value) {
        return 1;
        }
    

无论key,value为多少,恒返回1

一图胜千言

假设LruCache初始时的maxSize = 5

LruCache

注:这里省略了value,只显示每个元素的key