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當(dāng)然就是依據(jù) LRU 算法實(shí)現(xiàn)的緩存婆廊。簡單說就是迅细,設(shè)置好緩存大小淘邻;當(dāng)緩存空間不足的時候茵典,就把最近最少使用(也就是最長時間沒有使用)的緩存項(xiàng)清除掉;然后提供新的緩存宾舅。
LruCache 的使用其實(shí)比較簡單统阿,可以歸結(jié)為以下要點(diǎn):
- LruCache 內(nèi)部使用 LinkedHashMap 實(shí)現(xiàn),所以 LruCache 保存的是鍵值對
- LruCache 本身對緩存項(xiàng)是強(qiáng)引用
- LruCache 的讀寫是線程安全的筹我,內(nèi)部加了 synchronized扶平。也就是 put(K key, V value) 和 get(K key) 內(nèi)部有 synchronized
- key 和 value 不接受 null 。所以如果 get 到了 null 蔬蕊,那就說明是沒有緩存
- Override sizeOf(K key, V value) 方法
- 根據(jù)需要Override entryRemoved(boolean evicted, K key, V oldValue, V newValue) 和 create(K key) 方法
感覺已經(jīng)寫完了结澄。
好吧,還是從實(shí)際使用的角度展開一下吧岸夯。
-
從構(gòu)造函數(shù)就能看出來麻献,LruCache 內(nèi)部使用 LinkedHashMap 實(shí)現(xiàn)。在《JAVA 核心技術(shù) 卷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); }
并且,在構(gòu)造的時候就傳入了 int maxSize 旅赢,也就是緩存大小齿桃。maxSize 的具體含義,或者說單位鲜漩,是和 int sizeOf(K key, V value) 的返回值對應(yīng)的源譬。
LruCache 也提供了 resize(int maxSize) 方法重新設(shè)置緩存大小。 -
int sizeOf(K key, V value) 用來計算每一個緩存項(xiàng)的大小孕似〔饶铮或者,簡單點(diǎn)說喉祭,LruCache 是根據(jù)對每一個緩存項(xiàng)調(diào)用的 sizeOf 的返回值累加养渴,判斷當(dāng)前緩存空間是否超出 maxSize。
所以泛烙,實(shí)際使用 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(); } }
當(dāng)然,也可以不覆寫 sizeOf 方法蔽氨。這個方法默認(rèn)返回1藐唠。所以帆疟,比如說我很明確這里是要緩存32個圖片縮略圖,不是很在乎每個縮略圖具體的內(nèi)存大小宇立,就可以直接設(shè)置 maxSize 為32踪宠;而 sizeOf 默認(rèn)返回1就可以。
-
緩存的讀寫妈嘹,簡單點(diǎn)說就是 put(K key, V value) 和 V get(K key)柳琢。
不過,我覺得LruCache的代碼寫的不錯润脸,所以貼一下吧,學(xué)習(xí)學(xué)習(xí)毙驯。在put方法中可以看到倒堕,
- 首先,key 和 value都不可以為 null尔苦。
- 其次涩馆,如果(previous != null),說明該key之前對應(yīng)了一個value允坚,這時會調(diào)用 entryRemoved() 方法魂那。這默認(rèn)是一個空方法,我們可以根據(jù)需要 Override稠项。
- 還有就是涯雅,方法最后調(diào)用了 trimToSize(int maxSize) 方法,顧名思義展运,就是用來修剪緩存大小活逆,使之小于 maxSize。也就是刪除掉最近最少使用的緩存對象拗胜,直到 實(shí)際緩存 <= maxSize蔗候。trimToSize 方法在 while 循環(huán)內(nèi)加了 synchronized (this) 來實(shí)現(xiàn)刪除,代碼很簡單埂软。
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勘畔。
- 不然的話所灸,就會調(diào)用 create 方法去創(chuàng)建一個。所以炫七,我們可以根據(jù)需要去覆寫 create 方法爬立。這個方法默認(rèn)返回 null。
- 并且万哪,多線程的情況下侠驯,可能存在第一步 get 為 null抡秆,調(diào)用 create 方法后,其他線程已經(jīng) 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 對應(yīng)的 value 的情況下冲杀,構(gòu)造一個默認(rèn)值效床,并保存到緩存中。所以我們可以根據(jù)需要权谁,選擇是否覆寫這個方法剩檀。
entryRemoved 方法,是在 map 中有 value 被覆蓋旺芽,或者被刪除(trimToSize 方法中會刪除)的時候調(diào)用沪猴,我們也可以根據(jù)需要覆寫這個方法。
所以采章,LruCache 的使用真的很簡單运嗜,在不覆寫 sizeOf 方法的情況下,可以直接 new 一個悯舟,并指定 maxSize担租,然后 put 、get 就可以了抵怎。
LruCache 的源碼中奋救,還有一個地方我覺得寫得比較好。
那就是反惕,上面的代碼中都是調(diào)用的 safeSizeOf 方法尝艘,而不是 sizeOf 方法。
而我一直在說姿染,我們可以根據(jù)需要覆寫 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 雖然最終也是調(diào)用的 sizeOf隘梨,但是增加了
異常判斷。
這樣的好處是舷嗡, sizeOf 可以開放給最終的 API 使用者直接去覆寫轴猎;而代碼中,又不必到處都是對 sizeOf 的返回值的異常判斷进萄。
所以捻脖,LruCache 的整個代碼锐峭,我都覺得寫得很漂亮。