一句話解釋: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 中,并且把最近最少使用的對象在緩存值達到預設定值之前從記憶體中移除。
源碼解讀
OK老規矩,我先帶大家一起研讀下LruCache的源碼,我們重點看下get、put、Remove等方法,其實原理就是LinkedHashMap的機制。
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 <= ) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(, f, true);
}
/**
* Sets the size of the cache.
* @param maxSize The new maximum size.
*/
public void resize(int maxSize) {
if (maxSize <= ) {
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 < || (map.isEmpty() && size != )) {
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 = * * ; // 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的某個值需要明确釋放,重寫方法
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/8574102