??提到Android的緩存策略
任何一個Android開發(fā)人員都能隨口說出LruCache
莉钙,利用最新最少使用(Least Recently Used)
的原則進行緩存,可LRU
到底是怎么實現(xiàn)的呢?
LruCache簡介
??LruCache是個泛型類宁赤,主要算法原理是把最近使用的對象用強引用(即我們平常使用的對象引用方式)存儲在 LinkedHashMap 中
。當(dāng)緩存滿時贿讹,把最近最少使用的對象從內(nèi)存中移除苛聘,并提供了get
和put
方法來完成緩存的獲取和添加操作。
實現(xiàn)原理
LruCache內(nèi)部維護的是一個LinkedHashMap
秃流,最近最少的算法判斷邏輯都是由LinkedHashMap來實現(xiàn)赂蕴,如果對LinkedHashMap原理不了解的童鞋,可以先了解下LinkedHashMap的原理分析這篇文章剔应。
初始化
private final LinkedHashMap<K, V> map;
/** Size of this cache in units. Not necessarily the number of elements. */
private int size; //當(dāng)前cache的大小
private int maxSize; //cache最大大小
private int putCount; //put的次數(shù)
private int createCount; //create的次數(shù)
private int evictionCount; //回收的次數(shù)
private int hitCount; //命中的次數(shù)
private int missCount; //未命中次數(shù)
/**
* @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) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
//將LinkedHashMap的accessOrder設(shè)置為true來實現(xiàn)LRU
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
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); //size加上預(yù)put對象的大小
previous = map.put(key, value);
if (previous != null) {
//如果之前存在鍵為key的對象睡腿,則size應(yīng)該減去原來對象的大小
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
//每次新加入對象都需要調(diào)用trimToSize方法看是否需要回收
trimToSize(maxSize);
return previous;
}
由上面的代碼可以看出來,首先把size
增加峻贮,然后判斷是否以前已經(jīng)有元素席怪,如果有,就更新當(dāng)前的size,并且調(diào)用entryRemoved
方法,entryRemoved是一個空實現(xiàn)纤控,如果我們使用LruCache的時候需要掌握元素移除的信息挂捻,可以重寫這個方法。最后就會調(diào)用trimToSize
船万,來調(diào)整集合中的內(nèi)容刻撒。
trimToSize 方法
/**
* @param maxSize the maximum size of the cache before returning. May be -1
* to evict even 0-sized elements.
* 此方法根據(jù)maxSize來調(diào)整內(nèi)存cache的大小,如果maxSize傳入-1耿导,則清空緩存中的所有對象
*/
private 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!");
}
//如果當(dāng)前size小于maxSize或者map沒有任何對象,則結(jié)束循環(huán)
if (size <= maxSize || map.isEmpty()) {
break;
}
//移除鏈表頭部的元素声怔,并進入下一次循環(huán)
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++; //回收次數(shù)+1
}
entryRemoved(true, key, value, null);
}
}
這個方法是一個無限循環(huán),跳出循環(huán)的條件是舱呻,size < maxSize
或者 map 為空
醋火。主要的功能是判斷當(dāng)前容量時候已經(jīng)超出最大的容量,如果超出了maxSize的話箱吕,就會循環(huán)移除map中的第一個元素
芥驳,直到達到跳出循環(huán)的條件。由上面的分析知道茬高,map中的第一個元素就是最近最少使用的那個元素兆旬。
get
/**
* 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.
* 通過key獲取相應(yīng)的item,或者創(chuàng)建返回相應(yīng)的item怎栽。相應(yīng)的item會移動到隊列的尾部丽猬,
* 如果item的value沒有被cache或者不能被創(chuàng)建宿饱,則返回null。
*/
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) {
//mapValue不為空表示命中宝鼓,hitCount+1并返回mapValue對象
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.
* 如果未命中刑棵,則試圖創(chuàng)建一個對象,這里create方法返回null,并沒有實現(xiàn)創(chuàng)建對象的方法
* 如果需要事項創(chuàng)建對象的方法可以重寫create方法愚铡。因為圖片緩存時內(nèi)存緩存沒有命中會去
* 文件緩存中去取或者從網(wǎng)絡(luò)下載蛉签,所以并不需要創(chuàng)建。
*/
V createdValue = create(key);
if (createdValue == null) {
return null;
}
//假如創(chuàng)建了新的對象沥寥,則繼續(xù)往下執(zhí)行
synchronized (this) {
createCount++;
//將createdValue加入到map中碍舍,并且將原來鍵為key的對象保存到mapValue
mapValue = map.put(key, createdValue);
if (mapValue != null) {
// There was a conflict so undo that last put
//如果mapValue不為空,則撤銷上一步的put操作邑雅。
map.put(key, mapValue);
} else {
//加入新創(chuàng)建的對象之后需要重新計算size大小
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
//每次新加入對象都需要調(diào)用trimToSize方法看是否需要回收
trimToSize(maxSize);
return createdValue;
}
}
這個方法就先通過key來獲取value,如果能獲取到就就直接返回片橡,獲取不到的話,就調(diào)用create()
方法創(chuàng)建一個淮野,事實上捧书,如果我們不重寫這個create方法的話是return null
的,所以整個流程就是獲取得到就直接返回骤星,獲取不到就返回null
经瓷。
不要以為LruCache
是多么復(fù)雜的一個算法,看過之后是不是感覺也就那么回事洞难?-