Android中的LruCache的原理和使用
LruCache,雖然很多文章都把LRU翻譯成“最近最少使用”快取策略,但Android中的LruCache真的如此嗎?
答案是No,它只做到了控制“最近使用”!
原理
資料結構
LruCache採用LinkedHashMap作為儲存的資料結構,那麼為什麼是LinkedHashMap?
LinkedHashMap特性簡介
- LinkedHashMap基於HashMap,具有HashMap高效查詢、自動擴容等特性
- 在HashMap基礎上,增加了一個雙向連結串列儲存K-V對、實現了自己的遍歷器LinkedHashIterator,預設情況下可以做到根據資料插入順序有序地遍歷
- 提供過載構造方法供外部控制accessOrder,以實現根據訪問順序有序地遍歷
初始化
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);
}
LruCache的構造方法如上,可見LruCache初始化時就使用了上面提到的LinkedHashMap的第三點特性,即在資料結構層面實現了“最近使用”。
儲存
當呼叫put方法新增/設定儲存內容時,LruCache依次做了這麼幾件事:
- 判空,即不允許key/value為null
- 總容量size增加上計算傳入的K-V大小
- 將傳入的K-V存入LinkedHashMap
- 如過LinkedHashMap中已存在相同K,總容量size減去替換掉的K-VOld大小
- 通知VOld被替換(子類實現entryRemoved以監聽)
- 比較總容量size和最大容量maxSize,若大於maxSize則迴圈移除LinkedHashMap頭結點(即最久未被訪問的結點)直至size小於等於maxSize
獲取
當呼叫get方法獲取儲存內容時,LruCache依次做了這些事:
- 判空
- 從LinkedHashMap中取出與K對應的V值並返回。如果子類未實現create方法以達到當快取未命中時建立並存入新V的話,返回null,get流程結束。
- 通過create建立VNew,並嘗試把VNew儲存到LinkedHashMap中,流程類似儲存過程,不同之處在於當K衝突時,會捨棄掉新建立的VNew。不要奇怪為什麼明明上面通過K取V的時候沒取到,這裡卻會K衝突,因為LruCache為了效能考慮(防止子類自定義的create方法耗時過長影響get方法執行效能),只對從LinkedHashMap中取值的過程做了同步處理,這樣在多執行緒的情況下就可能出現A執行緒在create的時候,B執行緒已經將K-VB存入了map。
- 返回上面建立的VNew或者VB
使用
用LruCache實現一個簡單的圖片快取
class LruImageCache extends LruCache<String, Bitmap> {
private static final String TAG = "LruImageCache";
private static final int DEFAULT_CACHE_SIZE = 20 * 1024 * 1024;
public LruImageCache() {
this(DEFAULT_CACHE_SIZE);
}
public LruImageCache(int maxSize) {
super(maxSize);
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
Log.d(TAG, "cache removed: " + key);
}
@Override
protected Bitmap create(String key) {
// 從本地、網路獲取圖片
return loadImageFromIO(key);
}
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getAllocationByteCount();
}
}
//使用
Bitmap b = LruImageCache.get("http://image-path");