https://developer.android.google.cn/topic/performance/graphics/cache-bitmap.html
https://developer.android.google.cn/reference/android/util/LruCache.html
LRU (Least Recently Used) 就是最近最少使用算法,LruCache当然就是依据 LRU 算法实现的缓存。简单说就是,设置好缓存大小;当缓存空间不足的时候,就把最近最少使用(也就是最长时间没有使用)的缓存项清除掉;然后提供新的缓存。
LruCache 的使用其实比较简单,可以归结为以下要点:
- LruCache 内部使用 LinkedHashMap 实现,所以 LruCache 保存的是键值对
- LruCache 本身对缓存项是强引用
- LruCache 的读写是线程安全的,内部加了 synchronized。也就是 put(K key, V value) 和 get(K key) 内部有 synchronized
- key 和 value 不接受 null 。所以如果 get 到了 null ,那就说明是没有缓存
- Override sizeOf(K key, V value) 方法
- 根据需要Override entryRemoved(boolean evicted, K key, V oldValue, V newValue) 和 create(K key) 方法
感觉已经写完了。
好吧,还是从实际使用的角度展开一下吧。
-
从构造函数就能看出来,LruCache 内部使用 LinkedHashMap 实现。在《JAVA 核心技术 卷1》里面介绍 LinkedHashMap 的时候,就提到过 LinkedHashMap 可以用来做 LRU 算法。
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); }
并且,在构造的时候就传入了 int maxSize ,也就是缓存大小。maxSize 的具体含义,或者说单位,是和 int sizeOf(K key, V value) 的返回值对应的。
LruCache 也提供了 resize(int maxSize) 方法重新设置缓存大小。 -
int sizeOf(K key, V value) 用来计算每一个缓存项的大小。或者,简单点说,LruCache 是根据对每一个缓存项调用的 sizeOf 的返回值累加,判断当前缓存空间是否超出 maxSize。
所以,实际使用 LruCache 的时候,我们可能看到这样的代码:int cacheSize = 4 * 1024 * 1024; // 4MiB LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) { protected int sizeOf(String key, Bitmap value) { return value.getByteCount(); } }
当然,也可以不覆写 sizeOf 方法。这个方法默认返回1。所以,比如说我很明确这里是要缓存32个图片缩略图,不是很在乎每个缩略图具体的内存大小,就可以直接设置 maxSize 为32;而 sizeOf 默认返回1就可以。
-
缓存的读写,简单点说就是 put(K key, V value) 和 V get(K key)。
不过,我觉得LruCache的代码写的不错,所以贴一下吧,学习学习。在put方法中可以看到,
- 首先,key 和 value都不可以为 null。
- 其次,如果(previous != null),说明该key之前对应了一个value,这时会调用 entryRemoved() 方法。这默认是一个空方法,我们可以根据需要 Override。
- 还有就是,方法最后调用了 trimToSize(int maxSize) 方法,顾名思义,就是用来修剪缓存大小,使之小于 maxSize。也就是删除掉最近最少使用的缓存对象,直到 实际缓存 <= maxSize。trimToSize 方法在 while 循环内加了 synchronized (this) 来实现删除,代码很简单。
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) { entryRemoved(false, key, previous, value); } trimToSize(maxSize); return previous; }
get 方法写的也很漂亮。
- 如果能够从 map 中拿到缓存值,就直接 return。
- 不然的话,就会调用 create 方法去创建一个。所以,我们可以根据需要去覆写 create 方法。这个方法默认返回 null。
- 并且,多线程的情况下,可能存在第一步 get 为 null,调用 create 方法后,其他线程已经 put 了 key 的 value 的情况,所以就会把 被 create 方法的值覆盖的 mapValue 重新写回去。也就是有注释“// There was a conflict so undo that last put”的情况。
// 真心觉得 Google 代码写得漂亮。 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. */ V createdValue = create(key); if (createdValue == null) { return null; } synchronized (this) { createCount++; mapValue = map.put(key, createdValue); if (mapValue != null) { // There was a conflict so undo that last put map.put(key, mapValue); } else { size += safeSizeOf(key, createdValue); } } if (mapValue != null) { entryRemoved(false, key, createdValue, mapValue); return mapValue; } else { trimToSize(maxSize); return createdValue; } }
从上面的分析也能看出,create 方法是在缓存 map 中没有 key 对应的 value 的情况下,构造一个默认值,并保存到缓存中。所以我们可以根据需要,选择是否覆写这个方法。
entryRemoved 方法,是在 map 中有 value 被覆盖,或者被删除(trimToSize 方法中会删除)的时候调用,我们也可以根据需要覆写这个方法。
所以,LruCache 的使用真的很简单,在不覆写 sizeOf 方法的情况下,可以直接 new 一个,并指定 maxSize,然后 put 、get 就可以了。
LruCache 的源码中,还有一个地方我觉得写得比较好。
那就是,上面的代码中都是调用的 safeSizeOf 方法,而不是 sizeOf 方法。
而我一直在说,我们可以根据需要覆写 sizeOf 方法,这是为什么呢?
看看源码:
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 方法,safeSizeOf 虽然最终也是调用的 sizeOf,但是增加了
异常判断。
这样的好处是, sizeOf 可以开放给最终的 API 使用者直接去覆写;而代码中,又不必到处都是对 sizeOf 的返回值的异常判断。
所以,LruCache 的整个代码,我都觉得写得很漂亮。