LruCache 源码笔记整理

基于 Android API 25,android.util 包下 LruCache。

基础补充:LinkedHashMap 笔记整理


1、概述

LRU 是 Least Recently Used 的缩写,即最近最少使用。

而 LruCache 则是基于 LRU 算法实现的一个类,当缓存空间不足时,就会删除最老的元素(即最久没用过的),用于 Android 实现内存缓存。非线程安全的。

2、构造方法

// 存储的元素的大小总值,不一定等于元素个数
private int size;
// 能够缓存的最大值
private int 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);
}

通过其构造方法可以发现,内部是基于 LinkedHashMap 的,且 LinkedHashMap 的 accessOrder 被置为 true,因此 LinkedHashMap 中的元素是按照元素的访问顺序(即 get() 方法会影响元素的排序)来排序的,从最新到最老。

而 LRU 是最近最少使用,即当缓存空间不足的时候,会删除最少使用的,即最老的。因此,可想而知,删除 LinkedHashMap 时是从头部删起。

3、put(K key, V value)

public final V put(K key, V value) {
    // key 或者 value 不能为 null
    if (key == null || value == null) {
        throw new NullPointerException("key == null || value == null");
    }
    V previous;
    synchronized (this) {
        putCount++;
        // 先加上 value 对应的大小
        size += safeSizeOf(key, value);
        // 对于 map.put() 如果是替换原值,则会返回原来 oldValue
        //(当然也包括 oldValue 本来就为 null 的情况)
        previous = map.put(key, value);
        if (previous != null) {
            // 如果对应的 key 原来就有值,则将减去被替换的值的大小
            size -= safeSizeOf(key, previous);
        }
    }
    
    if (previous != null) {
        entryRemoved(false, key, previous, value);
    }
    
    // 移除最老的元素,直到缓存的元素总大小 <= maxSize
    trimToSize(maxSize);
    return previous;
}

// 当元素节点被移除的时候调用,默认空实现
// evicted 为 true 是表示这个元素是因为空间不够而被自动清理了;为 false 表示由于 put 或者 remove 而被移除的
// 所以可以在这里对这个被清理的元素进行额外操作,如再次缓存
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}

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;
            }
            // eldest() 为 Android 里面的额外实现,返回 map 的 head。
            // 因为对于 LinkedHashMap,是按照最老到最新的排序的。
            // 而 Lru 又是缓存最新使用的,因此从 head 开始移除元素
            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++;
        }
        entryRemoved(true, key, value, null);
    }
}

put() 方法的实现比较简单,即把 key-value 保存起来,并设置 size,最后调用 trimToSize() 方法确保 size <= maxSize

trimToSize() 中,会无限循环删除最老的元素,直到缓存的元素大小总和 <= maxSize,而在删除元素的时候,是删除最老的,即从 LinkedHashMap 中双向链表的头部 head 开始。

4、get(K key)

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++;
    }
    /*
     * Attempt to create a value. This may take a long time, and the map
     * may be different when create() returns. If a conflicting value was
     * added to the map while create() was working, we leave that value in
     * the map and release the created value.
     */
    // 尝试通过 create() 方法创建 key 对应的 value,因为可能花的时间长,
    // 此时 map 可能会不同于 create() 完成时。
    // 如果在 create() 工作的时候,为 key 添加了一个值,则新建的与添加的会存在冲突,
    // 此时使用添加的,把新建的抛弃掉
    V createdValue = create(key);
    if (createdValue == null) {
        return null;
    }
    synchronized (this) {
        createCount++;
        // 把 createdValue 存入 map 中
        // put 方法会把 key 对应的 oldValue 返回
        mapValue = map.put(key, createdValue);
        // 如果存入 createdValue 时,map 中已经又一个值了,
        // 则还是用原来的值。
        if (mapValue != null) {
            // There was a conflict so undo that last put
            map.put(key, mapValue);
        } else {
            // 如果使用的是新建的值,则改变 size
            size += safeSizeOf(key, createdValue);
        }
    }
    if (mapValue != null) {
        // 当元素被移除的时候调用
        entryRemoved(false, key, createdValue, mapValue);
        return mapValue;
    } else {
        // 如果是原来没有值,把新建的值置入了 map 中,则需要确保元素的总大小 <= maxSize
        trimToSize(maxSize);
        return createdValue;
    }
}

get() 方法的实现比较复杂,它会先根据 key 去 map 中取值,如果此时还没有缓存过对应的 key-value,则取值为 null,接着就会调用 create(key) 方法(默认为空实现,用于根据 key 生成对应的 value)。

但是 create() 方法可能耗时长,此时如果从外部存入了对应的 key-value,等 create() 完成之后,就有同时存在新建的添加的 两个值,这是冲突的,这个时候,就会优先使用 添加的 的值,把 新建的 给抛弃调。

通过 get() 方法,实际上会调用 map.get(),因为给 map 设置的 accessOrder 为 true,则会把访问到的元素给置于 LinkedHashMap 中双链表的尾部。

5、remove(K 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 -= safeSizeOf(key, previous);
        }
    }
    if (previous != null) {
        entryRemoved(false, key, previous, null);
    }
    return previous;
}

remove() 则会移除 key 对应的 value,如果该 value 存在,则对 size 减去相应的大小。

5、safeSizeOf(K key, V 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;
}

// 默认实现
protected int sizeOf(K key, V value) {
    return 1;
}

safeSizeOf() 是了计算 value 所占用的大小,其内部会调用 sizeOf(key, value),并做安全处理。

sizeOf(key, value) 方法则一般是需要我们自己覆盖实现的。


最后,实践一下 LruCache 的基本使用。
摘抄自:LruCache原理和用法与LinkedHashMap

val mLruCache: LruCache<String, Bitmap>
//获取手机最大内存 单位 kb
val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
//一般都将 1/8 设为 LruCache 的最大缓存
val cacheSize = maxMemory / 8

mLruCache = object : LruCache<String, Bitmap>(maxMemory / 8) {
    /**
     * 这个方法从源码中看出来是设置已用缓存的计算方式的。
     * 默认返回的值是1,也就是没缓存一张图片就将已用缓存大小加1.
     * 缓存图片看的是占用的内存的大小,每张图片的占用内存也是不一样的,一次不能这样算。
     * 因此要重写这个方法,手动将这里改为本次缓存的图片的大小。
     */
    override fun sizeOf(key: String, value: Bitmap): Int {
        return value.byteCount / 1024
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值