Android中的LruCache的原理和使用

行走的段子發表於2020-07-31

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依次做了這麼幾件事:

  1. 判空,即不允許key/value為null
  2. 總容量size增加上計算傳入的K-V大小
  3. 將傳入的K-V存入LinkedHashMap
  4. 如過LinkedHashMap中已存在相同K,總容量size減去替換掉的K-VOld大小
  5. 通知VOld被替換(子類實現entryRemoved以監聽)
  6. 比較總容量size和最大容量maxSize,若大於maxSize則迴圈移除LinkedHashMap頭結點(即最久未被訪問的結點)直至size小於等於maxSize

獲取

當呼叫get方法獲取儲存內容時,LruCache依次做了這些事:

  1. 判空
  2. 從LinkedHashMap中取出與K對應的V值並返回。如果子類未實現create方法以達到當快取未命中時建立並存入新V的話,返回null,get流程結束。
  3. 通過create建立VNew,並嘗試把VNew儲存到LinkedHashMap中,流程類似儲存過程,不同之處在於當K衝突時,會捨棄掉新建立的VNew。不要奇怪為什麼明明上面通過K取V的時候沒取到,這裡卻會K衝突,因為LruCache為了效能考慮(防止子類自定義的create方法耗時過長影響get方法執行效能),只對從LinkedHashMap中取值的過程做了同步處理,這樣在多執行緒的情況下就可能出現A執行緒在create的時候,B執行緒已經將K-VB存入了map。
  4. 返回上面建立的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");

相關文章