基于 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
}
}