標籤:android開發 frameworks 源碼 緩衝 cache
行動裝置開發中,由於行動裝置(手機等)的記憶體有限,所以使用有效緩衝技術是必要的.android提供來一個緩衝工具類LruCache,開發中我們會經常用到,下面來他是如何?的.
在package android.util包裡面有對LruCache定義的java檔案.為了能準確的理解LruCache,我們先來看看原文的說明:
* A cache that holds strong references to a limited number of values. Each time * a value is accessed, it is moved to the head of a queue. When a value is * added to a full cache, the value at the end of that queue is evicted and may * become eligible for garbage collection.
簡單翻譯:LruCache快取資料是採用持有資料的強引用來儲存一定數量的資料的.每次用到(擷取)一個資料時,這個資料就會被移動(一個儲存資料的)隊列的頭部,當往這個緩衝裡面加入一個新的資料時,如果這個緩衝已經滿了,就會自動刪除這個緩衝隊列裡面最後一個資料,這樣一來使得這個刪除的資料沒有強引用而能夠被gc回收.
從上面的翻譯,可以知道LruCache的工作原理.下面來一步一步說明他的具體實現:
(1)如何?儲存的資料是有一定順序的,並且使用過一個存在的資料,這個資料就會被移動到資料隊列的頭部.這裡採用的是LinkedHashMap.
我們知道LinkedHashMap是儲存一個鍵值對資料的,並且可以維護這些資料相應的順序的.一般可以保證儲存的資料按照存入的順序或者使用的順序的.下面來看看LruCache的構造方法:
public LruCache(int maxSize) {//指定快取資料的數量 if (maxSize <= 0) {//必須大於0 throw new IllegalArgumentException("maxSize <= 0"); } this.maxSize = maxSize; this.map = new LinkedHashMap<K, V>(0, 0.75f, true);//建立一個LinkedHashMap,並且按照訪問資料的順序排序 }
(2)如上面的構造方法可以知道,在建立一個LruCache就需要指定其快取資料的數量.這裡要詳細解釋一下這個快取資料的"數量"到底是指什麼:是指快取資料對象的個數呢,還是快取資料所佔用的記憶體總量呢?
答案是:都是. 可以是快取資料的個數,也可以使快取資料所佔用記憶體總量,當然也可以是其他.到底是什麼,需要看你的LruCache如何重寫這個方法:sizeOf(K key, V value)
protected int sizeOf(K key, V value) {//子類覆蓋這個方法來計算出自己的緩衝對於每一個儲存的資料所佔用的量 return 1;//預設返回1,這說明:預設情況下緩衝的數量就是指快取資料的總個數(每一個資料都是1). } 那如果我使用LruCache來儲存bitmap的圖片,並且希望緩衝的容量是4M那這麼做?在原文的說明中,android給來這樣一個執行個體:
* <p>By default, the cache size is measured in the number of entries. Override * {@link #sizeOf} to size the cache in different units. For example, this cache * is limited to 4MiB of bitmaps: * <pre> {@code * int cacheSize = 4 * 1024 * 1024; // 4MiB * LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {//儲存bitmap的LruCache,容量是4M * protected int sizeOf(String key, Bitmap value) { * return value.getByteCount();//計算每一個緩衝的圖片所佔用記憶體大小 * } * }}</pre>
(3)那麼LruCache如何,何時判斷是否緩衝已經滿來,並且需要移除不常用的資料呢?
其實在LruCache裡面有一個方法:trimToSize()就是用來檢測一次當前是否已經滿,如果滿來就自動移除一個資料,一直到不滿為止:
public void trimToSize(int maxSize) {//預設情況下傳入是上面說的最大容量的值 this.maxSize while (true) {//死迴圈.保證一直到不滿為止 K key; V value; synchronized (this) {//安全執行緒保證 if (size < 0 || (map.isEmpty() && size != 0)) { throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!"); } if (size <= maxSize) {//如果不滿,就跳出迴圈 break; } Map.Entry<K, V> toEvict = map.eldest();//取出最後的資料(最不常用的資料) if (toEvict == null) { break; } key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key);//移除這個資料 size -= safeSizeOf(key, value);//容量減少 evictionCount++;//更新自動移除資料的數量(次數) } entryRemoved(true, key, value, null);//用來通知這個資料已經被移除,如果你需要知道一個資料何時被移除你需要從寫這個方法entryRemoved } } 上面的源碼中我給出了說明,很好理解.這裡要注意的是trimToSize這個方法是public的,說明其實我們自己可以調用這個方法的.這一點很重要.記住他,你會用到的.
下面的問題是:trimToSize這個方法何時調用呢?
trimToSize這個方法在LruCache裡面多個方法裡面會被調用來檢測是否已經滿了,比如在往LruCache裡面加入一個新的資料的方法put裡面,還有在通過get(K key)這個方法擷取一個資料的時候等,都會調用trimToSize來檢測一次.下了來看看put裡面如何調用:
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; }
(4)細心的人在看了上面的源碼可以發現,原來對 LruCache的操作都加了synchronized來保證安全執行緒,是的,LruCache就是安全執行緒的,其他的方法也都使用來synchronized
(5)其實你應該馬上有一個疑問:如果LruCache中已經刪除了一個資料,可是現在又調用LruCache的get方法擷取這個資料怎麼辦?來看看源碼是否有解決這個問題:
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方法來重新建立已經不存在的資料.這個方法預設情況是什麼也不做的,所以需要你自己做
protected V create(K key) { return null; }
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
android之LruCache源碼解析