LruCache

源码讲解

Posted by Allen Vork on April 22, 2018

LruCache 是什么

Lru(Least Recently Used)近期最少使用算法是将近期最少使用的数据从缓存中移除。它主要是用于取代 SoftReference 的。其实 LruCache 内部仅仅是对 LinkedHashMap 的封装,Lru 也是由 LinkedHashMap 实现的。 LruCache 中做的主要操作是设置缓存大小,然后在往缓存放数据的时候,如果超过所设置的大小,就将不常用的数据移除掉。

源码解析

public class LruCache<K, V> {
    private final LinkedHashMap<K, V> map;

    /**
     * @param maxSize 指缓存的大小,默认是缓存的个数
     * 如果是 bitmap 的话,就要重写 sizeOf 方法,用于返回一个 bitmap 的大小,
     * 而 maxsize 就是所缓存的 bitmap 的总大小。
     */
    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);
    }

    //重点看下 get 方法
    public final V get(K key) {
        V mapValue;
        synchronized (this) {
            mapValue = map.get(key);
            if (mapValue != null) {
                hitCount++;
                // 在 map 中找到了就直接返回
                return mapValue;
            }
            missCount++;
        }

        /**
         *缓存中没找到的话就调用 create 去创建(这一步由子类实现),
         *這個方法沒加锁,具体可以看下面该方法的注释
         */
        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }

        //create 完成了
        synchronized (this) {
            createCount++;
            mapValue = map.put(key, createdValue);

            if (mapValue != null) {
                // mapValue 不为 null 说明在 create 的时候,该值(mapValue)已经被添加到缓存了
                // 那么就保留 mapValue,将 createValue 丢弃
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }

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

    /**
     * put 方法没什么特别的,需要注意的是它是将值放到队列头部
     */
    public final V put(K key, V value) {
        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;
    }

    /**
     * 缓存如果超过了最大值,则将旧数据移除
     */
    public void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
				// 如果 maxSize 很小(如 -1),那么就会清空缓存
                if (size <= maxSize || map.isEmpty()) {
                    break;
                }

                Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

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

    /**
     * 该方法没加锁(因为创建的方法一般不涉及到多个线程访问共享变量的问题),那么多个线程
     * 可以同时请求相同的数据的情况是存在的。那么就有了上面 get() 方法中 if (mapValue != null) 的判断
     */
    protected V create(K key) {
        return null;
    }
}

LruCache 类的核心代码就是上面那么多,可以看出和 Lru 算法没什么关系,我们看下简单的用法:

    // 一般图片缓存会使用最大内存的1/4或1/8
    val maxMemory = (Runtime.getRuntime().maxMemory() / 4) as Int
    val imgCache = object : LruCache<String, Bitmap>(maxMemory) {

        override fun sizeOf(key: String, value: Bitmap): Int {
            return value.byteCount
        }
    }
    val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher);
    imgCache.put("${R.mipmap.ic_launcher}$", bitmap)