關(guān)于Android內(nèi)存緩存LruCache的使用及其源碼解析
整體介紹
LruCache 作為內(nèi)存緩存,使用強(qiáng)引用方式緩存有限個(gè)數(shù)據(jù),當(dāng)緩存的某個(gè)數(shù)據(jù)被訪問時(shí),它就會(huì)被移動(dòng)到隊(duì)列的頭部,當(dāng)一個(gè)新數(shù)據(jù)要添加到LruCache而此時(shí)緩存大小要滿時(shí),隊(duì)尾的數(shù)據(jù)就有可能會(huì)被垃圾回收器(GC)回收掉,LruCache使用的LRU(Least Recently Used)算法,即:把最近最少使用的數(shù)據(jù)從隊(duì)列中移除,把內(nèi)存分配給最新進(jìn)入的數(shù)據(jù)。
- 如果LruCache緩存的某條數(shù)據(jù)明確地需要被釋放,可以覆寫
entryRemoved(boolean evicted, K key, V oldValue, V newValue) - 如果LruCache緩存的某條數(shù)據(jù)通過key沒有找到,可以覆寫
create(K key),這簡化了調(diào)用代碼,即使錯(cuò)過一個(gè)緩存數(shù)據(jù),也不會(huì)返回 null,而會(huì)返回通過create(K key) 創(chuàng)造的數(shù)據(jù)。 - 如果想限制每條數(shù)據(jù)的緩存大小,可以覆寫
sizeOf(K key, V value),如下面設(shè)置bitmap最大緩存大小是4MB:
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();
}
}LruCache 這個(gè)類是線程安全的。自動(dòng)地執(zhí)行多個(gè)緩存操作通過synchronized 同步緩存:
synchronized (cache) {
if (cache.get(key) == null) {
cache.put(key, value);
}
}LruCache不允許 null 作為一個(gè) key 或 value,get(K)、put(K,V),remove(K) 中如果key 或 value 為 null,都會(huì)拋出異常:throw new NullPointerException("key == null || value == null")。
常用API
| 方法 | 備注 |
|---|---|
| void resize(int maxSize) | 更新存儲(chǔ)大小 |
| V put(K key, V value) | 存數(shù)據(jù),返回之前key對應(yīng)的value,如果沒有,返回null |
| V get(K key) | 取出key對應(yīng)的緩存數(shù)據(jù) |
| V remove(K key) | 移除key對應(yīng)的value |
| void evictAll() | 清空緩存數(shù)據(jù) |
| Map<K, V> snapshot() | 復(fù)制一份緩存并返回,順序從最近最少訪問到最多訪問排序 |
使用示例
- 初始化:
private Bitmap bitmap;
private String STRING_KEY = "data_string";
private LruCache<String, Bitmap> lruCache;
private static final int CACHE_SIZE = 10 * 1024 * 1024;//10M
lruCache = new LruCache<String, Bitmap>(CACHE_SIZE) {
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
super.entryRemoved(evicted, key, oldValue, newValue);
}
@Override
protected int sizeOf(String key, Bitmap value) {
//這里返回的大小用單位kb來表示的
return value.getByteCount() / 1024;
}
};最大緩存設(shè)置為10M,key是String類型,value設(shè)置的是Bitmap
- 存數(shù)據(jù):
if (lruCache!=null){
lruCache.put(STRING_KEY, bitmap);
}- 取數(shù)據(jù):
if (lruCache != null) {
bitmap = lruCache.get(STRING_KEY);
}- 清除緩存:
源碼分析
定義變量、構(gòu)造器初始化
//LruCache.java
private final LinkedHashMap<K, V> map;//使用LinkedHashMap來存儲(chǔ)操作數(shù)據(jù)
/** Size of this cache in units. Not necessarily the number of elements. */
private int size;//緩存數(shù)據(jù)大小,不一定等于元素的個(gè)數(shù)
private int maxSize;//最大緩存大小
private int putCount;// 成功添加數(shù)據(jù)的次數(shù)
private int createCount;//手動(dòng)創(chuàng)建緩存數(shù)據(jù)的次數(shù)
private int evictionCount;//成功回收數(shù)據(jù)的次數(shù)
private int hitCount;//查找數(shù)據(jù)時(shí)命中次數(shù)
private int missCount;//查找數(shù)據(jù)時(shí)未命中次數(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;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}構(gòu)造函數(shù)中初始化了 LinkedHashMap,參數(shù)maxSize指定了緩存的最大大小。
修改緩存大小
/**
* 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);
}resize(int maxSize) 用來更新大小,先是加同步鎖更新maxSize的值,接著調(diào)用了trimToSize()方法:
/**
* 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!");
}
//如果已經(jīng)小于最大緩存,則無需接著往下執(zhí)行了
if (size <= maxSize) {
break;
}
//拿到最近最少使用的那條數(shù)據(jù)
Map.Entry<K, V> toEvict = map.eldest();
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
//從LinkedHashMap移除這條最少使用的數(shù)據(jù)
map.remove(key);
//緩存大小size減去移除數(shù)據(jù)的大小,如果沒有覆寫sizeOf,則減去的值是1
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "=" + value);
}
return result;
}
/**
* Returns the size of the entry for {@code key} and {@code value} in
* user-defined units. The default implementation returns 1 so that size
* is the number of entries and max size is the maximum number of entries.
*
* <p>An entry's size must not change while it is in the cache.
*/
protected int sizeOf(K key, V value) {
return 1;
}
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}trimToSize() 循環(huán)移除最近最少使用的數(shù)據(jù)直到剩余緩存數(shù)據(jù)的大小等于小于最大緩存大小。 注:我們看到sizeOf()和entryRemoved()都是protected來修飾的,即可以被覆寫,如果sizeOf()沒有被覆寫,那么變量size 代表的是緩存數(shù)據(jù)的數(shù)量,maxSize代表的是最大數(shù)量,如果覆寫sizeOf(),如:
@Override
protected int sizeOf(String key, BitmapDrawable value) {
return value.getBitmap().getByteCount() / 1024;
}此時(shí)size 代表的是緩存數(shù)據(jù)的大小,maxSize代表的是最大緩存大小。
存數(shù)據(jù)
/**
* 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) {
//key或value為空直接拋異常
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
//添加數(shù)據(jù)次數(shù)+1
putCount++;
//緩存數(shù)據(jù)的大小增加
size += safeSizeOf(key, value);
//添加緩存數(shù)據(jù),添加之前如果key值對應(yīng)的value不為空,則newValue會(huì)覆蓋oldValue,并返回oldValue;
//如果key值對應(yīng)的value為空,則返回Null
previous = map.put(key, value);
if (previous != null) {
//之前的oldValue不為空,則在緩存size中減去oldValue
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
//重新檢查緩存大小
trimToSize(maxSize);
return previous;
}調(diào)用 LinkedHashMap 的 put(key, value) 添加緩存數(shù)據(jù)后,在添加之前如果 key 值對應(yīng)的value 不為空,則 newValue 會(huì)覆蓋 oldValue ,并返回 oldValue;如果 key 值對應(yīng)的 value 為空,則返回 null,接著根據(jù)返回值來重新設(shè)置緩存 size 和最大緩存 maxSize 的大小。
取數(shù)據(jù)
/**
* 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) {
//通過key取數(shù)據(jù)
mapValue = map.get(key);
if (mapValue != null) {
//如果取到了數(shù)據(jù),命中次數(shù)+1
hitCount++;
return mapValue;
}
//沒有取到數(shù)據(jù),未命中此時(shí)+1
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.
*/
//如果沒有覆寫create(),默認(rèn)create()方法返回的null
V createdValue = create(key);
if (createdValue == null) {
return null;
}
//如果覆寫了create(),即根據(jù)key值手動(dòng)創(chuàng)造了value,則繼續(xù)往下執(zhí)行
synchronized (this) {
//創(chuàng)造數(shù)據(jù)次數(shù)+1
createCount++;
//嘗試將數(shù)據(jù)添加到緩存中
mapValue = map.put(key, createdValue);
if (mapValue != null) {
// There was a conflict so undo that last put
//mapValue不為空,說明之前的key對應(yīng)的是有數(shù)據(jù)的,那么就跟我們手動(dòng)創(chuàng)建的數(shù)據(jù)沖突了,
//所以執(zhí)行撤消操作,重新把mapValue添加到緩存中,用mapValue去覆蓋createdValue
map.put(key, mapValue);
} else {
//如果mapValue為空,說明之前的key值對應(yīng)的value確實(shí)為空,我們手動(dòng)添加createdValue后,
//需要重新計(jì)算緩存size的大小
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
protected V create(K key) {
return null;
}取數(shù)據(jù)的流程大致是這樣:
- 首先通過
map.get(key)來嘗試取出value,如果value存在,則直接返回value; - 如果
value不存在,則執(zhí)行下面的create(key),如果create()沒有被覆寫,則直接返回null; - 如果
create()被覆寫了,即通過key值創(chuàng)建了一個(gè)createdValue,那么嘗試通過mapValue = map.put(key, createdValue)把createdValue添加到緩存中去,mapValue是put()方法的返回值,mapValue代表的是key之前對應(yīng)的值,如果mapValue不為空,說明之前的key對應(yīng)的是有數(shù)據(jù)的,那么就跟我們手動(dòng)創(chuàng)建的數(shù)據(jù)沖突了,所以執(zhí)行撤消操作,重新把mapValue添加到緩存中,用mapValue去覆蓋createdValue,最后再重新計(jì)算緩存大小。
移除數(shù)據(jù)
/**
* 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;
}調(diào)用 map.remove(key) 通過 key 值刪除緩存中 key 對應(yīng)的value,然后重新計(jì)算緩存大小,并返回刪除的value。
其他一些方法:
//清空緩存
public final void evictAll() {
trimToSize(-1); // -1 will evict 0-sized elements
}
public synchronized final int size() {
return size;
}
public synchronized final int maxSize() {
return maxSize;
}
public synchronized final int hitCount() {
return hitCount;
}
public synchronized final int missCount() {
return missCount;
}
public synchronized final int createCount() {
return createCount;
}
public synchronized final int putCount() {
return putCount;
}
public synchronized final int evictionCount() {
return evictionCount;
}
/**
* Returns a copy of the current contents of the cache, ordered from least
* recently accessed to most recently accessed.
*/
//復(fù)制一份緩存,順序從最近最少訪問到最多訪問排序
public synchronized final Map<K, V> snapshot() {
return new LinkedHashMap<K, V>(map);
}以上就是關(guān)于Android內(nèi)存緩存LruCache的使用及其源碼解析的詳細(xì)內(nèi)容,更多關(guān)于Android LruCache的使用的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android中監(jiān)聽軟鍵盤顯示狀態(tài)實(shí)現(xiàn)代碼
這篇文章主要介紹了Android中監(jiān)聽軟鍵盤顯示狀態(tài)實(shí)現(xiàn)代碼,本文直接給出核心實(shí)現(xiàn)代碼,需要的朋友可以參考下2015-04-04
關(guān)于Android Activity之間傳遞數(shù)據(jù)的6種方式
這篇文章主要介紹了關(guān)于Android Activity之間傳遞數(shù)據(jù)的6種方式,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-03-03
Kotlin中空判斷與問號和感嘆號標(biāo)識符使用方法
最近使用kotlin重構(gòu)項(xiàng)目,遇到了一個(gè)小問題,在Java中,可能會(huì)遇到判斷某個(gè)對象是否為空,為空執(zhí)行一段邏輯,不為空執(zhí)行另外一段邏輯,下面這篇文章主要給大家介紹了關(guān)于Kotlin中空判斷與問號和感嘆號標(biāo)識符處理操作的相關(guān)資料,需要的朋友可以參考下2022-12-12
Ubuntu中為Android系統(tǒng)上編寫Linux內(nèi)核驅(qū)動(dòng)程序?qū)崿F(xiàn)方法
本文主要介紹在Ubuntu 上為Android系統(tǒng)編寫Linux內(nèi)核驅(qū)動(dòng)程序, 這里對編寫驅(qū)動(dòng)程序做了詳細(xì)的說明,對研究Android源碼和HAL都有巨大的幫助,有需要的小伙伴可以參考下2016-08-08
Android 進(jìn)度條自動(dòng)前進(jìn)效果的實(shí)現(xiàn)代碼
這篇文章主要介紹了Android 進(jìn)度條自動(dòng)前進(jìn)效果,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07
Android超詳細(xì)講解組件AdapterView的使用
AdapterView組件是一組重要的組件,AdapterView本身是一個(gè)抽象基類,它派生的子類在用法上十分相似,從AdapterView派生出的三個(gè)子類:AdsListView、AdsSpinner、AdapterViewAnimator,這3個(gè)子類依然是抽象的,實(shí)際運(yùn)用時(shí)需要它們的子類2022-03-03

