LruCache的源碼分析已經(jīng)很多了,看了很多遍,但是自己走一遍分析,才是真正的掌握,將知識(shí)轉(zhuǎn)化到自身。
用途
LruCache的用途就是緩存,但是緩存并不能一直無限量的緩存,設(shè)備內(nèi)存始終是有限的,所以緩存需要有一個(gè)刪除策略。
常見例子
圖片加載基本是每個(gè)App的必備項(xiàng)了,雖然我們使用都是第三方加載框架,但是加載框架內(nèi)部也會(huì)緩存圖片Bitmap對(duì)象,也是使用LruCache來緩存,所以我們了解LruCache,對(duì)加載框架的緩存邏輯也能知道核心了。
- 啟動(dòng)一個(gè)AsyncTask任務(wù),OkHttp加載圖片Bitmap,從網(wǎng)絡(luò)獲取前先從LruCache中查詢是否有緩存,有則直接返回,無則進(jìn)行網(wǎng)絡(luò)請(qǐng)求,請(qǐng)求完畢再保存到LruCache緩存中,下次再去獲取則會(huì)命中緩存,復(fù)用緩存而無需請(qǐng)求。
- 創(chuàng)建圖片緩存,緩存大小為設(shè)備最大內(nèi)存容量的8分之一。創(chuàng)建LruCache,我們一般需要復(fù)寫sizeOf(),LruCache會(huì)回調(diào)sizeOf()來獲取緩存對(duì)象占用的內(nèi)存大小。
還有2個(gè)可選復(fù)寫的方法:
- entryRemoved()為緩存對(duì)象被刪除時(shí)的回調(diào)。
- create()為獲取不到緩存時(shí)調(diào)用,我們可以新建緩存對(duì)象,但在圖片緩存中我們并不需要,返回null或者不復(fù)寫即可(默認(rèn)就是返回null),請(qǐng)求完網(wǎng)絡(luò)后再保存,或者在這里請(qǐng)求再返回,但是不建議,畢竟每個(gè)緩存對(duì)象的獲取方式不同。
/**
* 創(chuàng)建圖片緩存
*/
private void createImageCache() {
//取設(shè)備內(nèi)存最大容量的8分之一作為緩存大小
long maxMemorySize = Runtime.getRuntime().maxMemory();
int cacheSize = (int) (maxMemorySize / 8);
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
//計(jì)算Bitmap占用的內(nèi)存
return getBitmapSize(value);
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
super.entryRemoved(evicted, key, oldValue, newValue);
Log.d(TAG, "緩存對(duì)象被刪除");
}
@Override
protected Bitmap create(String key) {
//get()方法獲取不到緩存時(shí)調(diào)用,我們不返回?cái)?shù)據(jù),讓去請(qǐng)求網(wǎng)絡(luò)獲取
return super.create(key);
}
};
}
- 新建AsyncTask異步任務(wù),doInBackground()中,先查詢緩存是否存在,不存在再讓請(qǐng)求網(wǎng)絡(luò),再保存到網(wǎng)絡(luò),如果緩存存在,則直接復(fù)用緩存。
/**
* 兼容獲取Bitmap大小
*/
private int getBitmapSize(Bitmap bitmap) {
//API 19
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
return bitmap.getAllocationByteCount();
}
//API 12
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.HONEYCOMB_MR1) {
return bitmap.getByteCount();
}
// Earlier Version
return bitmap.getRowBytes() * bitmap.getHeight();
}
private static class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
private OkHttpClient mClient;
private Callback mCallback;
/**
* 回調(diào)接口
*/
public interface Callback {
/**
* 執(zhí)行前回調(diào)
*/
void onStart();
/**
* 執(zhí)行后回調(diào)
*
* @param bitmap 執(zhí)行結(jié)果
*/
void onFinish(Bitmap bitmap);
/**
* 嘗試獲取緩存
*/
Bitmap getCache(String key);
}
public DownloadImageTask(OkHttpClient client, Callback callback) {
mClient = client;
mCallback = callback;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
mCallback.onStart();
}
@Override
protected Bitmap doInBackground(String... urls) {
try {
String url = urls[0];
//1.先從內(nèi)存緩存中找
Bitmap cacheBitmap = mCallback.getCache(url);
if (cacheBitmap != null) {
Log.d(TAG, "緩存命中");
return cacheBitmap;
}
Log.d(TAG, "緩存不命中,請(qǐng)求網(wǎng)絡(luò)");
//2.緩存找不到,請(qǐng)求網(wǎng)絡(luò)
Request request = new Request.Builder()
.url(url)
.build();
Call call = mClient.newCall(request);
Response response = call.execute();
return BitmapFactory.decodeStream(response.body().byteStream());
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
mCallback.onFinish(bitmap);
}
}
原理
LruCache的緩存是使用的最近最少使用的策略,當(dāng)訪問元素時(shí),將元素移動(dòng)到表尾,當(dāng)緩存容量到達(dá)最大值時(shí)移除表頭元素(最少使用的元素)。
使用Key-Value的方式緩存數(shù)據(jù)自然想到Map這種鍵值對(duì)的數(shù)據(jù)結(jié)構(gòu),而LruCache的策略則使用了LinkedHashMap。
為什么使用LinkedHashMap呢?因?yàn)長(zhǎng)inkedHashMap的構(gòu)造方法,有一個(gè)accessOrder參數(shù),默認(rèn)為false,則為按插入順序排序,true則使用訪問順序排序,訪問越多,越排得后。使用LinkedHashMap則天然支持最近最少使用的策略。
構(gòu)造方法。
- 指定最大容量為參數(shù)創(chuàng)建。不允許配置小于等于0.
- 并創(chuàng)建了一個(gè)LinkedHashMap,指定最后的accessOrder字段為true,則代表按訪問順序排序,將經(jīng)常訪問的元素放到表尾。
/**
* @param maxSize 最大緩存容量
*/
public LruCache(int maxSize) {
//最大緩存的對(duì)象大小,不能小于等于0,否則拋出異常
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
//緩存映射,最后的accessOrder字段設(shè)置為true,則按照訪問的順序排序,否則以插入的順序排序
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
添加緩存元素。
- key、value都不能為null,否則拋出異常。
- 同步鎖,處理并發(fā),safeSizeOf()就是調(diào)用到我們的sizeOf()獲取緩存對(duì)象的占用大小。并將占用大小和已占用大小累加。
- 通過map.put()添加緩存元素。put()方法有一個(gè)返回值,意思是如果之前已經(jīng)有key保存了,則將之前保存的value返回,再覆蓋上新的value。
- 如果put()有返回值,則將他的占用大小和累加值相減。
- 由于刪除了舊值,所以回調(diào)entryRemoved()。
- put()操作改變了緩存,調(diào)用trimToSize()調(diào)整內(nèi)存。
/**
* 添加緩存
*
* @param key 緩存Key
* @param value 緩存值
*/
public final V put(K key, V value) {
//不能存儲(chǔ)null鍵和null值
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
//以前的值,只有當(dāng)map里面已經(jīng)記錄了key,通過put方法就會(huì)返回之前緩存的value
V previous;
synchronized (this) {
//插入的數(shù)量自增
putCount++;
//將已緩存的對(duì)象大小和本次添加的緩存對(duì)象的大小相加
size += safeSizeOf(key, value);
//添加緩存
previous = map.put(key, value);
//之前添加過,將大小減回去
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
//由于本次緩存會(huì)覆蓋之前的值,所以相當(dāng)于刪除之前的值,回調(diào)entryRemoved通知元素被刪除
if (previous != null) {
entryRemoved(false, key, previous, value);
}
//每次調(diào)整緩存,都去判斷如果緩存滿了,按照LRU最近最少使用策略刪除緩存
trimToSize(maxSize);
return previous;
}
修整緩存占用
- 開啟一個(gè)死循環(huán),不斷檢查當(dāng)前占用內(nèi)存是否大于最大值,否則一直循環(huán)刪除最近最少使用的對(duì)象,直到內(nèi)存占用小于最大值才停止。
- map遍歷,找出表頭元素。
- 通過map.remove()移除最近最少使用的元素。
- 由于刪除了元素,調(diào)用safeSizeOf()調(diào)整內(nèi)存計(jì)數(shù)。
- 回調(diào)entryRemoved()提示刪除了元素。
/**
* 整理緩存,如果內(nèi)存超出,刪除表頭元素
*/
private void trimToSize(int maxSize) {
//開啟死循環(huán)
while (true) {
K key;
V value;
synchronized (this) {
//參數(shù)檢查
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
//一直死循環(huán),直到緩存的對(duì)象內(nèi)存小于最大值
if (size <= maxSize) {
break;
}
//不斷獲取第一個(gè)元素,不斷while循環(huán)刪除,直到內(nèi)存大小比最大內(nèi)存大小小才停下
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
//如果為null不處理
if (toEvict == null) {
break;
}
//獲取本次要?jiǎng)h除的元素鍵值
key = toEvict.getKey();
value = toEvict.getValue();
//移除元素
map.remove(key);
//將刪除的元素的內(nèi)存減去
size -= safeSizeOf(key, value);
//回收次數(shù)自增
evictionCount++;
}
//調(diào)用entryRemoved()提醒刪除了元素
entryRemoved(true, key, value, null);
}
}
獲取緩存元素
- 不允許key為null,否則拋出異常。
- 同步鎖包保證并發(fā)獲取。
- 通過map.get(key)獲取緩存元素。
- 獲取不到緩存元素,則調(diào)用create(key)來新建元素。
- create()獲取新建的元素不為null,則將元素保存到map,處理就和上面的put()處理流程是一致的。
- 操作完畢,調(diào)用trimToSize(),檢查是否需要清理內(nèi)存。
/**
* 獲取緩存,會(huì)將本次獲取的元素放到表尾
*/
public final V get(K key) {
//不允許key為null
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
//從map中獲取value,由于我們?cè)O(shè)置了accessOrder為true,所以會(huì)將元素移動(dòng)到表尾
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
//map中獲取不到元素,調(diào)用create()方法創(chuàng)建一個(gè)
V createdValue = create(key);
if (createdValue == null) {
return null;
}
//將對(duì)象緩存到map和put()方法是一樣的
synchronized (this) {
createCount++;
//put添加緩存,但是如果返回了舊值,則將舊值保存回去(意外)
mapValue = map.put(key, createdValue);
if (mapValue != null) {
//保存原來的值
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
//有舊值,從map中刪除掉
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
//每次調(diào)整緩存,都去判斷如果緩存滿了,按照LRU最近最少使用策略刪除緩存
trimToSize(maxSize);
return createdValue;
}
}
移除緩存元素
- 同樣key不能為null,否則拋出異常。
- 通過map.remove(key)移除元素
- 調(diào)用safeSizeOf()計(jì)算內(nèi)存占用大小。
- 調(diào)用entryRemoved()通知緩存元素被刪除。
/**
* 移除元素
*
* @return 元素Key
*/
public final V remove(K key) {
//不允許key為null
if (key == null) {
throw new NullPointerException("key == null");
}
//移除的元素
V previous;
synchronized (this) {
//從map中移除元素
previous = map.remove(key);
//改變緩存的大小
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
//回調(diào)entryRemoved告知元素被刪除
if (previous != null) {
entryRemoved(false, key, previous, null);
}
return previous;
}
完整源碼和注釋
public class LruCache<K, V> {
private final LinkedHashMap<K, V> map;
/**
* 當(dāng)前緩存的對(duì)象的總內(nèi)存
*/
private int size;
/**
* 配置的最大緩存容量
*/
private int maxSize;
/**
* 調(diào)用put()方法緩存對(duì)象的次數(shù)
*/
private int putCount;
/**
* 調(diào)用create()方法創(chuàng)建對(duì)象的次數(shù)
*/
private int createCount;
/**
* 調(diào)用trimToSize()方法回收對(duì)象的次數(shù)
*/
private int evictionCount;
/**
* 取調(diào)用get()命中緩存的次數(shù)
*/
private int hitCount;
/**
* 調(diào)用get()方法不命中的次數(shù)
*/
private int missCount;
/**
* @param maxSize 最大緩存容量
*/
public LruCache(int maxSize) {
//最大緩存的對(duì)象大小,不能小于等于0,否則拋出異常
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
//緩存映射,最后的accessOrder字段設(shè)置為true,則按照訪問的順序排序,否則以插入的順序排序
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
/**
* 重新設(shè)置最大緩存大小
*
* @param maxSize 新的緩存大小
*/
public void resize(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
//同步鎖同步設(shè)置最大值
synchronized (this) {
this.maxSize = maxSize;
}
trimToSize(maxSize);
}
/**
* 獲取緩存,會(huì)將本次獲取的元素放到表尾
*/
public final V get(K key) {
//不允許key為null
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
//從map中獲取value,由于我們?cè)O(shè)置了accessOrder為true,所以會(huì)將元素移動(dòng)到表尾
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
//map中獲取不到元素,調(diào)用create()方法創(chuàng)建一個(gè)
V createdValue = create(key);
if (createdValue == null) {
return null;
}
//將對(duì)象緩存到map和put()方法是一樣的
synchronized (this) {
createCount++;
//put添加緩存,但是如果返回了舊值,則將舊值保存回去(意外)
mapValue = map.put(key, createdValue);
if (mapValue != null) {
//保存原來的值
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
//有舊值,從map中刪除掉
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
//每次調(diào)整緩存,都去判斷如果緩存滿了,按照LRU最近最少使用策略刪除緩存
trimToSize(maxSize);
return createdValue;
}
}
/**
* 添加緩存
*
* @param key 緩存Key
* @param value 緩存值
*/
public final V put(K key, V value) {
//不能存儲(chǔ)null鍵和null值
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
//以前的值,只有當(dāng)map里面已經(jīng)記錄了key,通過put方法就會(huì)返回之前緩存的value
V previous;
synchronized (this) {
//插入的數(shù)量自增
putCount++;
//將已緩存的對(duì)象大小和本次添加的緩存對(duì)象的大小相加
size += safeSizeOf(key, value);
//添加緩存
previous = map.put(key, value);
//之前添加過,將大小減回去
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
//由于本次緩存會(huì)覆蓋之前的值,所以相當(dāng)于刪除之前的值,回調(diào)entryRemoved通知元素被刪除
if (previous != null) {
entryRemoved(false, key, previous, value);
}
//每次調(diào)整緩存,都去判斷如果緩存滿了,按照LRU最近最少使用策略刪除緩存
trimToSize(maxSize);
return previous;
}
/**
* 整理緩存,如果內(nèi)存超出,刪除表頭元素
*/
private void trimToSize(int maxSize) {
//開啟死循環(huán)
while (true) {
K key;
V value;
synchronized (this) {
//參數(shù)檢查
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
//一直死循環(huán),直到緩存的對(duì)象內(nèi)存小于最大值
if (size <= maxSize) {
break;
}
//不斷獲取第一個(gè)元素,不斷while循環(huán)刪除,直到內(nèi)存大小比最大內(nèi)存大小小才停下
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
//如果為null不處理
if (toEvict == null) {
break;
}
//獲取本次要?jiǎng)h除的元素鍵值
key = toEvict.getKey();
value = toEvict.getValue();
//移除元素
map.remove(key);
//將刪除的元素的內(nèi)存減去
size -= safeSizeOf(key, value);
//回收次數(shù)自增
evictionCount++;
}
//調(diào)用entryRemoved()提醒刪除了元素
entryRemoved(true, key, value, null);
}
}
/**
* 移除元素
*
* @return 元素Key
*/
public final V remove(K key) {
//不允許key為null
if (key == null) {
throw new NullPointerException("key == null");
}
//移除的元素
V previous;
synchronized (this) {
//從map中移除元素
previous = map.remove(key);
//改變緩存的大小
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
//回調(diào)entryRemoved告知元素被刪除
if (previous != null) {
entryRemoved(false, key, previous, null);
}
return previous;
}
/**
* 告知元素被刪除,我們可以復(fù)寫這個(gè)方法
*/
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {
}
/**
* 獲取不到元素,告知我們創(chuàng)建一個(gè)
*/
protected V create(K key) {
return null;
}
/**
* 實(shí)際就是調(diào)用了sizeOf()方法
*/
private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "=" + value);
}
return result;
}
/**
* 返回對(duì)象的內(nèi)存占用大小,默認(rèn)為,一般我們都會(huì)重寫
*/
protected int sizeOf(K key, V value) {
return 1;
}
/**
* 清除所有緩存
*/
public final void evictAll() {
//-1代表清除所有元素
trimToSize(-1);
}
/**
* 獲取已緩存的對(duì)象總大小
*/
public synchronized final int size() {
return size;
}
/**
* 獲取配置的對(duì)象緩存最大大小
*/
public synchronized final int maxSize() {
return maxSize;
}
/**
* 獲取調(diào)用get()命中緩存的次數(shù)
*/
public synchronized final int hitCount() {
return hitCount;
}
/**
* 獲取調(diào)用get()方法不命中的次數(shù)
*/
public synchronized final int missCount() {
return missCount;
}
/**
* 獲取調(diào)用create()方法創(chuàng)建對(duì)象的次數(shù)
*/
public synchronized final int createCount() {
return createCount;
}
/**
* 獲取調(diào)用put()方法緩存對(duì)象的次數(shù)
*/
public synchronized final int putCount() {
return putCount;
}
/**
* 獲取調(diào)用trimToSize()方法回收對(duì)象的次數(shù)
*/
public synchronized final int evictionCount() {
return evictionCount;
}
/**
* 獲取緩存map的一個(gè)副本
*/
public synchronized final Map<K, V> snapshot() {
return new LinkedHashMap<K, V>(map);
}
@Override
public synchronized final String toString() {
int accesses = hitCount + missCount;
int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
maxSize, hitCount, missCount, hitPercent);
}
}
總結(jié)
LruCache的最近最少使用的策略是通過LinkedHashMap,將LinkedHashMap的構(gòu)造方法的accessOrder字段設(shè)置true,讓LinkedHashMap按訪問順序排序,不斷將常用的元素放到表尾,不常用的元素放到表頭。每次增、刪、改都判斷緩存內(nèi)存是否大于最大緩存值,如果大于則用一個(gè)死循環(huán)不斷將表頭元素移除,直到內(nèi)存小于最大內(nèi)存。