LruCache原始碼分析

bigggfish發表於2018-02-01

使用強引用來快取有限數量的值.每次被訪問的值,將會移動到佇列的頭部。當佇列大小超過快取大小時,佇列尾部的值將會被刪除並且允許垃圾回收器將它回收。

如果你需要明確知道快取資料什麼時候被釋放,需要重寫 entryRemoved();方法

如果出現快取缺失時,可以重寫create();方法。使用這種簡單的方法,可以確保永遠存在返回值。

預設的,快取大小是根據快取項數量來計算的。可以重寫sizeOf()方法來設定不同的計算快取大小的單位。例如用於快取4MiB的圖片:

int cacheSize = 4 * 1024 * 1024;//用於計算快取總Size
LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize){
  protected int sizeOf(String key, Bitmap value){
    return value.getByteCount();//返回每一張圖片Size
  }
}
複製程式碼

這個類是執行緒安全的,多個快取的原子操作程式碼:

syschronized(cache){
  if(cache.get(key) == null){
    cache.put(key, value);
  }
}
複製程式碼

這個類不允許 null 作為key或者value。如果get(),put()remove()方法中返回值為null,那麼就明確表明,快取中不存在這個值。

這個類在Anroid3.1加入,如果需要在之前版本中使用,請使用support包中程式。

成員變數

private final LinkedHashMap<K, V> map;//Cache 快取的主要是實現基礎。

private int size;//基於某個單位上的快取大小,不一定等於快取成員數量。具體數量和size的轉換,通過方法sizeOf()實現。
private int maxSize;//初始化時傳入的,快取最大值。

private int putCount;//put()方法呼叫次數。
private int createCount;//create()方法呼叫次數。
private int evictionCount;//超出限制,被刪除的數量。
private int hitCount;//返回get()方法正常獲取快取資料次數。
private int missCount;//返回get()方法為在快取中獲取資料的次數。
複製程式碼

構造方法

public LruCache(int maxSize) {
      this.maxSize = maxSize;
      this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
  }
複製程式碼

  在建構函式中,初始化了成員變數中的LinkedHashMap()。LRUCache主要實現感覺更多的是LinkedHashMap的學習。 LinkedHashMap是HashMap增加一個雙向連結串列。 所以LinkedHashMap可以插入順序或者訪問順序訪問。 LinkedHashMap構造引數中最後一個引數accessOrder,用來判斷是使用哪種順序。accressOrder等於true為訪問順序, 等於false 為資料插入順序。

  LinkedHashMap重寫了HashMap的Entry。增加了before和after,使Entry雖然存在HashMap中但是另外也存在一個雙向連結串列。因此LinkedHashMap可以方便的通過訪問和插入順序來訪問某個Entry。也正是因為LinkedHashMap存在這個訪問資料順序序,所以用它來做LRUCache,方便刪除最早之前加入的資料,並且可以根據訪問順序來調整連結串列順序。

關於LinkedHashMap推薦部落格:http://blog.csdn.net/justloveyou_/article/details/71713781

get()方法

public final V get(K key) {
       V mapValue;
       synchronized (this) {                 
         //從map中查詢資料
           mapValue = map.get(key);   
           if (mapValue != null) {
             //存在則直接返回。並且查詢成功計數變數hitCount++
               hitCount++;
               return mapValue;
           }
           //查詢失敗計數變數missCount++
           missCount++;                    
       }
       /*
        * 下面嘗試使用 create()方法建立一個新值,加到到map中。而且如果新值存在衝突,將會保留以前的值。
        * create()方法預設返回值為null。
        */
       V createdValue = create(key);
       if (createdValue == null) {
           return null;
       }
       synchronized (this) {
           createCount++;
           //將建立的值新增到map中,並返回key以前對應的value。
           mapValue = map.put(key, createdValue);    
           //key以前存在對應值,則將以前的值重新新增到map中,捨棄新建立值。
           //否則將size增加新建value 所佔size。
           if (mapValue != null) {
               map.put(key, mapValue);
           } else {
               size += safeSizeOf(key, createdValue);
           }
       }

      //mapValue不為null,使用mapValue替換了createValue,呼叫entryRemoved()。
      //為null,則重新計算是否越界。
       if (mapValue != null) {
           entryRemoved(false, key, createdValue, mapValue);
           return mapValue;
       } else {
           trimToSize(maxSize);
           return createdValue;
       }
   }

複製程式碼

put方法

  /**
   * 將鍵值對插入到佇列頭部。
   * @return 返回以前與這個key相對應的Value。
   */
  public final V put(K key, V value) {

      V previous;
      synchronized (this) {
          putCount++;
          size += safeSizeOf(key, value);       //當前總size加上新加入Entry的size
          previous = map.put(key, value);       //加入Map中
           //如果以前與key相對應value不為空,那麼size減去那個value佔據size。
          if (previous != null) {             
              size -= safeSizeOf(key, previous);
          }
      }

      //因為刪除了previous  所以呼叫entryRemoved()方法。
      if (previous != null) {
          entryRemoved(false, key, previous, value);
      }

      trimToSize(maxSize);
      return previous;
  }
複製程式碼

remove()方法

   /**
    * 移除被 key對應的entry,如果不存在返回null。
    */
   public final V remove(K key) {

       V previous;
       synchronized (this) {
           previous = map.remove(key);
           if (previous != null) {
               size -= safeSizeOf(key, previous);
           }
       }

       if (previous != null) {
           entryRemoved(false, key, previous, null);
       }

       return previous;
   }

複製程式碼

resize()方法和trimToSize()方法和evictAll()

  /**
   * 設定快取size
   */
  public void resize(int maxSize) {
      if (maxSize <= 0) {
          throw new IllegalArgumentException("maxSize <= 0");
      }

      synchronized (this) {
          this.maxSize = maxSize;
      }
      trimToSize(maxSize);
  }

  /**
   * 刪除超出MaxSize的最早加入的entry。
   */
  public void trimToSize(int maxSize) {
      while (true) {
          K key;
          V value;
          synchronized (this) {

              if (size <= maxSize) {   
                  break;
              }

              //返回map中最古老的entry,如果map為空則返回null。          
              Map.Entry<K, V> toEvict = map.eldest();
              if (toEvict == null) {
                  break;
              }

              key = toEvict.getKey();
              value = toEvict.getValue();
              //從map中移除最古老的entry
              map.remove(key);       
              //size進行相應的減少
              size -= safeSizeOf(key, value);
              //修改被移除次數
              evictionCount++;
          }
          //因為最古老的entry的唄刪除了,所以呼叫entryRemoved().
          entryRemoved(true, key, value, null);
      }
  }

  /**
   * 清空快取
   */
  public final void evictAll() {
      trimToSize(-1); // -1 will evict 0-sized elements
  }


複製程式碼

部分protected 方法


    /**
    * 返回entry中每個value對應的在總size中佔據的大小。上面說明中通過重寫該方法,
    * 實現一個用於快取Bitmap的LruCache
    */
    protected int sizeOf(K key, V value) {
      return 1;
    }
    /**
    *當entry被remove或者被put方法替換或者被刪除最古老值時 呼叫此方法
    *evicted 為true為刪除掉最古老值時呼叫 ;false為remove()或者put()方法中呼叫。
    */
    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}

    /**
     *當呼叫get方法,沒有與key相對的值時調動。
     */
    protected V create(K key) {
        return null;
    }
複製程式碼

相關文章