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");
