一、LruCache是什么
該類最開(kāi)始出現(xiàn)在Android 3.1中瓷式,是高速緩存敷搪,其中包含對(duì)有限數(shù)量的值的強(qiáng)引用迁央。每次值被訪問(wèn)時(shí),它都會(huì)移到隊(duì)列的開(kāi)頭征椒。將值添加到緩存中后如果緩存滿了著淆,該隊(duì)列末尾的值將被逐出菇存,并且可能會(huì)引發(fā)垃圾回收却盘。
默認(rèn)情況下狰域,緩存大小以條目數(shù)衡量。重寫 sizeOf以不同的單位調(diào)整緩存大小黄橘。如下例兆览,此緩存限制存放4MiB的Bitmap:
int cacheSize = 4 * 1024 * 1024; // 4MiB
LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
}
二、解析源碼
構(gòu)造方法:
/**
* @param maxSize 對(duì)于不覆蓋sizeOf的緩存塞关,這是緩存中的最大條目數(shù)抬探。
* 對(duì)于所有其他緩存,這是此緩存中條目大小的最大和描孟。
*/
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);
}
使用了LinkHashMap作為實(shí)際緩存的容器驶睦,其構(gòu)造方法的第三個(gè)參數(shù)是accessOrder,設(shè)置為true表示按照訪問(wèn)順序排序匿醒。
public void resize(int maxSize)
設(shè)置緩存的大小
LruCache#get
返回key的值(如果它存在于緩存中或可以由create創(chuàng)建)。如果返回值缠导,則將其移到隊(duì)列的開(kāi)頭廉羔。如果未緩存值并且無(wú)法創(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) {
hitCount++;
return mapValue;
}
missCount++;
}
/*
* 嘗試創(chuàng)建一個(gè)值憋他。這可能會(huì)花費(fèi)很長(zhǎng)時(shí)間孩饼,并且當(dāng)create()返回時(shí),map可能會(huì)有所不同竹挡。
* 如果在create()工作時(shí)將沖突值添加到map中镀娶,則將該值保留在map中并釋放創(chuàng)建的值。
*/
V createdValue = create(key);
if (createdValue == null) {
return null;
}
synchronized (this) {
createCount++;
mapValue = map.put(key, createdValue);
if (mapValue != null) {
// 發(fā)生了沖突揪罕,因此撤消前面插入的創(chuàng)建的值
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
LruCache#put
為key緩存value梯码。該值將插入到隊(duì)列的開(kāi)頭。
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;
}
LruCache#trimToSize
刪除最舊的條目好啰,直到剩余條目總數(shù)等于或小于請(qǐng)求的大小轩娶。
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) {
break;
}
//調(diào)用了LinkedHashMap#eldest來(lái)獲取最近最少使用的條目
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);
}
}
public final V remove(K key)
刪除key條目(如果存在)
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
當(dāng)有條目被移除或調(diào)用put導(dǎo)致值被替換時(shí)會(huì)調(diào)用該方法,默認(rèn)是空實(shí)現(xiàn)
evicted參數(shù)為true表示是緩存滿了為了騰出空間而移除的框往,其它情況為false
protected V create(K key)
當(dāng)key未命中緩存時(shí)調(diào)用該方法生成對(duì)應(yīng)key的值鳄抒,默認(rèn)返回null
protected int sizeOf(K key, V value)
以用戶定義的單位返回key和value的條目大小。默認(rèn)實(shí)現(xiàn)返回1椰弊,因此size是條目數(shù)许溅,max size是最大條目數(shù)。
public final void evictAll()
清除緩存秉版,對(duì)每個(gè)已刪除條目調(diào)用entryRemoved
三贤重、LinkedHashMap
最近最少使用的順序是由LinkedHashMap來(lái)確保的,使用了一個(gè)雙向鏈表
/**
* 雙向鏈表的頭(最老)
*/
transient LinkedHashMapEntry<K,V> head;
/**
* 雙鏈表的末尾(最秀迤)
*/
transient LinkedHashMapEntry<K,V> tail;
/**
* 此LinkedHashMap的迭代排序方法:true 表示訪問(wèn)順序游桩, false 表示插入順序。
* @serial
*/
final boolean accessOrder;
前面提到LruCache初始化LinkedHashMap時(shí)accessOrder為true耐朴,按訪問(wèn)順序排序借卧。
accessOrder為true的情況下,雙向鏈表的頭是最近最少使用的條目筛峭,尾是最近最多使用的條目铐刘。
我們看看這個(gè)accessOrder在哪里起到了作用
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
在LinkedHashMap#get方法中,accessOrder為true就調(diào)用LinkedHashMap#afterNodeAccess
void afterNodeAccess(Node<K,V> e) { // 將節(jié)點(diǎn)移到最后
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;
}
}
在put的過(guò)程中影晓,如果HashMap中不存在對(duì)應(yīng)鍵的條目镰吵,就用了newNode來(lái)生成新的條目,LinkedHashMap重寫了該方法挂签,把新條目連接到鏈表末尾
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMapEntry<K,V> p =
new LinkedHashMapEntry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
// 連接到鏈表末尾
private void linkNodeLast(LinkedHashMapEntry<K,V> p) {
LinkedHashMapEntry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
前面提到移除條目時(shí)調(diào)用LinkedHashMap#eldest獲取最近最少使用條目疤祭,該方法直接返回了head
public Map.Entry<K, V> eldest() {
return head;
}
四、實(shí)現(xiàn)一個(gè)簡(jiǎn)易LruCache
import java.util.LinkedHashMap;
import java.util.Map;
public class LruCache<K,V> extends LinkedHashMap<K,V>{
int cacheSize;
public LruCache(int cacheSize){
super((int) Math.ceil(cacheSize / 0.75),0.75f,true);
this.cacheSize = cacheSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size()>cacheSize;
}
}
繼承了LinkedHashMap饵婆,調(diào)用父類構(gòu)造方法勺馆,設(shè)置雙向鏈表按訪問(wèn)順序排序。
重寫 LinkedHashMap#removeEldestEntry 方法,當(dāng) size 大于指定緩存容量時(shí)草穆,就返回了true灌灾,LinkedHashMap就會(huì)刪掉最舊的元素。