LruCache 源码解析

1、简述
  • LruCahce 其 Lru 是最近最少使用算法,是包含对有限数量值的强引用的缓存。每当一个值被访问,它将被移到队尾。当缓存达到指定的数量时,位于队头的值将被移除,并且可能被 GC 回收。如果缓存的值包含需要显式释放的资源,那么需要重写 entryRemoved() 方法。如果 key 对应的缓存未命中,通过重写 create() 方法创建对应的 value。
  • LruCache 的实现最要是借助于 LinkedHashMap 来实现的。如果对 LinkedHashMap 还不是很了解的同学可以去看之前我分享的 LinkedHashMap 源码解析,这样更利于更快的阅读和掌握 LruCache。
  • 通常用来进行图片 Bitmap 对象缓存,常用于各种图片加载框架。
2、归纳
  • LruCahce 基于最近最少使用算法,主要是通过 LinkedHashMap 并知道 accessOrder 字段为 true 来实现的。
  • key 和 value 都不允许为 null,负责抛出异常。
  • 添加了对象锁,单线程和多线程都安全。
3、分析
3.1、成员变量
public class LruCache<K, V> {
    
    // LruCache 主要借助 LinkedHashMap 按元素访问顺序的迭代顺序(此时 accessOrder = true)来实现
    private final LinkedHashMap<K, V> map;

    // 不同 key-value 条目下缓存的大小,不一定是 key-value 条目的数量 
    private int size;
    // 缓存大小的最大值
    private int maxSize;

    // 存储的 key-value 条目的个数
    private int putCount;
    // 创建 key 对应的 value 的次数
    private int createCount;
    // 缓存移除的次数
    private int evictionCount;
    // 缓存命中的次数
    private int hitCount;
    // 缓存未命中的次数
    private int missCount;
    ...

}
3.2、构造函数
public class LruCache<K, V> {
    
    ... 
    
    /**
     * maxSize 对于缓存没有重写 sizeOf 方法的时候,这个数值指定了缓存中可以容纳的最大条目的数量;
     * 对于其他缓存,这是缓存中条目大小的最大总和。
     */
    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;

        // 指定了哈希表初始容量为0,负载因子为0.75,迭代顺序为按照条目访问顺序
        // 因此在有对条目进行访问的操作的时候,条目都会被放置到队尾,具体细节详看 LinkedHashMap 的解析
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }
    
    ...

}
3.3、增操作
public class LruCache<K, V> {
    
    ... 
    
    /**
     * 对于 key,缓存其相应的 value,key-value 条目放置于队尾
     *
     * @return 返回先前 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) {
            // evicted 为 true,表明不是为了腾出空间而进行的删除操作
            entryRemoved(false, key, previous, value);
        }

        trimToSize(maxSize);
        return previous;
    }
    
    ...

}

3.4、删操作
public class LruCache<K, V> {
    
    ... 
    
    /**
     * 删除 key 对应的条目
     * <p>
     * 返回 key 对应的 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;
    }

    /**
     * 当条目需要被移除或删除时调用
     * 当一个值被移除以腾出空间,通过调用 remove 删除,或者被 put 调用替换值时,会调用此方法。默认实现什么也不做
     * <p>
     * 这个方法在被调用的时候没有添加额外的同步操作,因此其他线程可能在这个方法执行时访问缓存
     *
     * @param evicted  true 表明条目正在被删除以腾出空间,false 表明删除是由 put 或 remove 引起的(并非是为了腾出空间)
     * @param newValue key 的新值。如果非 null,则此删除是由 put 引起的。否则它是由 remove引起的
     */
    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) { }
    
    ...

}

3.5、查操作
public class LruCache<K, V> {
    
    ... 
    
    /**
     * 指定 key 对应的 value 值存在时返回,否则通过 create 方法创建相应的 key-value 对。
     * 如果对应的 value 值被返回,那么这个 key-value 对将被移到队尾。
     * 当返回 null 时,表明没有对应的 value 值并且也无法被创建
     *
     * @param key 键
     * @return 返回查找后的值
     */
    public final V get(K key) {
        // 缓存不允许 key 值为 null,因此对于查询 null 的键可直接抛出异常
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;
        synchronized (this) {
            mapValue = map.get(key);
            // 缓存命中
            if (mapValue != null) {
                hitCount++;
                return mapValue;
            }
            // 缓存未命中
            missCount++;
        }

        // 尝试创建一个 value 值,这可能需要花费较长的时间完成,当 create 返回时,哈希表可能变得不同
        // 如果在 create 工作时向哈希表添加了一个冲突的值(key 已经有对应的 value 值,但 create 方法返回了一个不同的 value 值)
        // 那么将该值保留在哈希表中并释放创建的值。
        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }

        synchronized (this) {
            // 缓存创建的次数
            createCount++;
            mapValue = map.put(key, createdValue);

            if (mapValue != null) {
                // mapValue 不为 null,说明存在一个冲突值,保留之前的 value 值
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            trimToSize(maxSize);
            return createdValue;
        }
    }
    
    ...

}

4、Size 操作
public class LruCache<K, V> {
    
    ... 
    
    /**
     * 以用户定义的单位返回 key-value 条目的大小
     * 默认实现返回1,因此 size 是条目数,max size是最大条目数
     * 条目的大小在缓存中时不得更改
     *
     * @param key   键
     * @param value 元素值
     * @return 1
     */
    protected int sizeOf(K key, V value) {
        return 1;
    }

    /**
     * 安全的 sizeOf
     *
     * @param key   键
     * @param value 元素值
     * @return 返回 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;
    }

    /**
     * 删除最旧的条目,直到剩余条目总数小于等于指定的大小
     *
     * @param maxSize 最大 size
     */
    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;
                }

                // 获取哈希表中最旧的条目
                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++;
            }

            // 此处 evicted 为 true,表明是为了腾出空间而进行的删除条目操作
            entryRemoved(true, key, value, null);
        }
    }

    /**
     * 调整缓存大小
     *
     * @param maxSize 最大 size
     */
    public void resize(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }

        synchronized (this) {
            this.maxSize = maxSize;
        }
        trimToSize(maxSize);
    }
    
    ...

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值