一句話解釋:LruCache(least recently used cache)最近最少使用緩存。
前面,咱們一塊兒學習了LinkedHashMap數據結構,那麼LruCache就是LinkedHashMap的最佳實踐,童鞋們能夠查看個人博客線性表數據結構解讀(六)鏈式哈希表結構-LinkedHashMap學習一下。
在平常開發中,咱們常常會使用一種內存緩存技術,即軟引用或弱引用 (SoftReference or WeakReference)。可是如今已經再也不推薦使用這種方式了,由於從 Android 2.3 (API Level 9)開始,垃圾回收器會更傾向於回收持有軟引用或弱引用的對象,這讓軟引用和弱引用變得再也不可靠。另外,Android 3.0 (API Level 11)中,圖片的數據會存儲在本地的內存當中,於是沒法用一種可預見的方式將其釋放,這就有潛在的風險形成應用程序的內存溢出並崩潰。
而谷歌大概從SDK21開始,提供LruCache這個工具類(此類在android-support-v4的包中提供) ,用於做爲實現內存緩存技術的解決方案。這個類很是適合用來緩存圖片,它的主要算法原理是把最近使用的對象用強引用存儲在 LinkedHashMap 中,而且把最近最少使用的對象在緩存值達到預設定值以前從內存中移除。java
源碼解讀
OK老規矩,我先帶你們一塊兒研讀下LruCache的源碼,咱們重點看下get、put、Remove等方法,其實原理就是LinkedHashMap的機制。android
public class LruCache<K, V> { private final LinkedHashMap<K, V> map;// 聲明一個LinkedHashMap private int size;// 已經存儲的數量大小 private int maxSize;// 規定的最大存儲空間 private int putCount;// put的次數 private int createCount;// create的次數 private int evictionCount;// 回首的次數 private int hitCount;// 命中的次數 private int missCount;// 丟失的次數 /** * 指定最大內存的LruCache構造方法 * @param maxSize for caches that do not override {@link #sizeOf}, this is * the maximum number of entries in the cache. For all other caches, * this is the maximum sum of the sizes of the entries in this cache. */ public LruCache(int maxSize) {// 官方推薦maxSize通常聲明爲手機內存的1/8 if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } this.maxSize = maxSize; this.map = new LinkedHashMap<K, V>(0, 0.75f, true); } /** * Sets the size of the cache. * @param maxSize The new maximum size. */ public void resize(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } synchronized (this) { this.maxSize = maxSize; } trimToSize(maxSize); } /** * Returns the value for {@code key} if it exists in the cache or can be * created by {@code #create}. If a value was returned, it is moved to the * head of the queue. This returns null if a value is not cached and cannot * be created. */ 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; } } /** * Caches {@code value} for {@code key}. The value is moved to the head of * the queue. * @return the previous value mapped by {@code key}. */ 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; } /** * 移除最老的元素,直到剩餘元素數量等於或小於請求所需的大小 * Remove the eldest entries until the total of remaining entries is at or * below the requested size. * @param maxSize the maximum size of the cache before returning. May be -1 * to evict even 0-sized elements. */ 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!"); } if (size <= maxSize || map.isEmpty()) { break; } Map.Entry<K, V> toEvict = map.entrySet().iterator().next(); key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= safeSizeOf(key, value); evictionCount++; } entryRemoved(true, key, value, null); } } /** * 移除已存在的元素實體 * Removes the entry for {@code key} if it exists. * @return the previous value mapped by {@code key}. */ 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; } …… }
上面的關於LruCache初始化分配緩存大小有多少,能夠參考下面幾個因素:算法
- 你的設備能夠爲每一個應用程序分配多大的內存?
- 設備屏幕上一次最多能顯示多少張圖片?有多少圖片須要進行預加載,由於有可能很快也會顯示在屏幕上?
- 你的設備的屏幕大小和分辨率分別是多少?
- 圖片的尺寸和大小,還有每張圖片會佔據多少內存空間?
- 圖片被訪問的頻率有多高?會不會有一些圖片的訪問頻率比其它圖片要高?若是有的話,你也許應該讓一些圖片常駐在內存當中,或者使用多個LruCache 對象來區分不一樣組的圖片。
基本使用
Cache保存一個強引用來限制內容數量,每當Item被訪問的時候,此Item就會移動到隊列的頭部。當cache已滿的時候加入新的item時,在隊列尾部的item會被回收。緩存
int cacheSize = 4 * 1024 * 1024; // 4MiB LruCache bitmapCache = new LruCache(cacheSize) { protected int sizeOf(String key, Bitmap value) { return value.getByteCount(); } }
建立一個大小爲4M的存儲空間來進行圖片的存儲,存儲按照隊列的形式,後存儲進來的和最新使用過的將會放在隊列的最後,這樣陳舊數據放在隊列的開始,用於GC的回收。安全
synchronized (cache) { if (cache.get(key) == null) { cache.put(key, value); }}
這個方法也展現了怎樣規範化的使用以及獲取由LruCache保存的數據,因爲這個類是線程安全的因此須要加上同步塊來進行存放數據,經過get和put方式來進行數據的存取,這點跟Map是一致的,put時若是鍵相同則會進行數據的覆蓋,可是有點須要注意這裏key和value都不能爲空,這裏跟Map有點區別。
還必須注意必需要主動的釋放資源,若是你cache的某個值須要明確釋放,重寫方法markdown
entryRemoved (boolean evicted, K key, V oldValue, V newValue)
若是資源是被系統回收的則evicted會返回TRUE,若是是由put,remove的方式替換回收的則evicted會返回FALSE,而後怎麼知道是經過put仍是remove的,能夠經過對newValue是否爲空進行判斷,若是爲空則是put調用,而後將remove和系統回收時將資源置爲空,就要本身去實現了。
若是key相對應的item丟掉啦,重寫create().這簡化了調用代碼,即便丟失了也總會返回。默認cache大小是測量的item的數量,重寫sizeof計算不一樣item的大小。數據結構
參考連接:http://blog.csdn.net/linghu_java/article/details/8574102app