LruCache
文章主要介紹了:
1.LruCache的基本使用
2.LruCache的原始碼分析
3.基於LinkedHashMap的實現
一、LruCache快取的例項程式碼。
假設存的是圖片· longmaxMemory = (int) Runtime.getRuntime().maxMemory(); int memorySize = (int) (maxMemory/8); LruCache<String ,Bitmap> lruCache = new LruCache<String ,Bitmap>(memorySize){ @Override protected int sizeOf(String key, Bitmap value) { return value.getByteCount(); } };
maxMemory
是快取定義的最大的值,不能超過這個值,負責就會被回收。
重寫sizeOf
是為了計算每個數值的大小,等累計快取的數值超出定義的最大值就會回收。
- 操作方式:
// 把Value物件加入到快取中 public void putValueToMemory(String key, Bitmap bitmap) { if (getValueFromMemory(key) == null) { lruCache.put(key, bitmap); } } // 從快取中得到value物件 public Bitmap getValueFromMemory(String key) { return lruCache.get(key); } // 從快取中刪除指定的value public void removeValueMemory(String key) { lruCache.remove(key); }
二 、原始碼呼叫
(1)屬性
public class LruCache<K, V> { private final LinkedHashMap<K, V> map; 核心類 /** Size of this cache in units. Not necessarily the number of elements. */ private int size; private int maxSize; private int putCount; private int createCount; private int evictionCount; private int hitCount; private int missCount;
LinkedHashMap為核心類,Lru也是基於LinkedHashMap從而實現的。
(2)LruCache只有一個構造方法,初始了LinkedHashMap
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); }
LinkedHashMap自身是預設按照插入的順序進行排序的,此處初始化的時候new LinkedHashMap<K, V>(0, 0.75f, true);
傳入true則將LinkedHashMap的順序改為訪問順序,說明Lru初始化的時候預設的是訪問順序。
(3)LruCache的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) {對map進行操作之前,先進行同步操作 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);判斷是否需要移除LinkedHashMap中的元素 return previous; }
safeSizeOf
方法,是計算LruCache的已經快取的大小.
entryRemoved(false, key, previous, value);
如果找的到對應的key的話是替換新值,不存在的話就是新增。true為刪除條目生成空間,false反之.
trimToSize
判斷是否需要移除LinkedHashMap中的元素
public void trimToSize(int 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!"); } **如果當前的size小於等於最大的size則直接返回,不需要刪除資料 if (size <= maxSize) { break; } **如果size超出了最大的size,則需要進行刪除資料, **map.eldest獲取表頭的資料,進行刪除 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); } }
如同上述註釋中寫的一樣,可以看出,就是不斷的迴圈移除LinkedHashMap雙向連結串列表頭的元素,直到快取的大小小於等於快取大小為止。
- map.eldest()返回值
public Map.Entry<K, V> eldest() { return head; }
LinkedHashMap map.eldest()的呼叫是返回表頭的集合資料head。
LinkedHashMap繼承HashMap,重寫的方法中並沒有重寫put,所以put使用的還是HashMap的put方法,在LruCache類中呢,put相當於呼叫的是HashMap的put,get呼叫的是LinkedHashMap重寫的get,此處主要就看一下這個put和get方法。在HashMap put方法中,最終會回撥afterNodeAccess給LinkedHashMap,在LinkedHashMap的get方法中,最終也是走的這個方法,來操作head集合的賦值,最近的操作的資料則移到表尾。
- LinkedHashMap中head的處理
void afterNodeAccess(Node<K,V> e) {move node to last LinkedHashMapEntry<K,V> last; if (accessOrder && (last = tail) != e) { LinkedHashMapEntry<K,V> p = (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after; p.after = null; if (b == null) head = a; else b.after = a; if (a != null) a.before = b; else last = b; if (last == null) head = p; else { p.before = last; last.after = p; } tail = p; ++modCount; } }
對LinkedHashMap的put和get操作,都會讓被操作的Entry移動到雙向連結串列的表尾,刪除資料則是從表頭開始的,就符合Lru演算法的要求。
(4)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++; } V createdValue = create(key); if (createdValue == null) { 通過Key嘗試建立一個新值,預設返回為null。可重寫 return null; } synchronized (this) { createCount++; 如果我們重寫了create(key)方法而且返回值不為空,那麼將上述的key與這個返回值寫入到map當中 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; } }
V createdValue = create(key);可重寫create,此處是判斷當前是值是不是空的,如果不是空的,會將建立的key與createdValue 值捆綁,存到map中。
(5)LruCache的remove方法是從快取中去刪除:
public final V remove(K key) { if (key == null) { throw new NullPointerException("key == null"); } 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; }
根據Key刪除所對應的value值。
總結:
Lru是基於LinkedHashMap實現的, 預設為訪問順序。
HashMap無序,而LinkedHashMap是有序的。序列可分為插入順序和訪問順序,若是訪問順序,操作已存在的資料時會將其移植雙鏈表表尾。