關(guān)于Android內(nèi)存緩存LruCache的使用及其源碼解析
整體介紹
LruCache 作為內(nèi)存緩存,使用強(qiáng)引用方式緩存有限個數(shù)據(jù),當(dāng)緩存的某個數(shù)據(jù)被訪問時,它就會被移動到隊列的頭部,當(dāng)一個新數(shù)據(jù)要添加到LruCache而此時緩存大小要滿時,隊尾的數(shù)據(jù)就有可能會被垃圾回收器(GC)回收掉,LruCache使用的LRU(Least Recently Used)算法,即:把最近最少使用的數(shù)據(jù)從隊列中移除,把內(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)用代碼,即使錯過一個緩存數(shù)據(jù),也不會返回 null,而會返回通過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
這個類是線程安全的。自動地執(zhí)行多個緩存操作通過synchronized
同步緩存:
synchronized (cache) { if (cache.get(key) == null) { cache.put(key, value); } }
LruCache不允許 null 作為一個 key 或 value,get(K)、put(K,V),remove(K)
中如果key 或 value 為 null,都會拋出異常:throw new NullPointerException("key == null || value == null")
。
常用API
方法 | 備注 |
---|---|
void resize(int maxSize) | 更新存儲大小 |
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來存儲操作數(shù)據(jù) /** Size of this cache in units. Not necessarily the number of elements. */ private int size;//緩存數(shù)據(jù)大小,不一定等于元素的個數(shù) private int maxSize;//最大緩存大小 private int putCount;// 成功添加數(shù)據(jù)的次數(shù) private int createCount;//手動創(chuàng)建緩存數(shù)據(jù)的次數(shù) private int evictionCount;//成功回收數(shù)據(jù)的次數(shù) private int hitCount;//查找數(shù)據(jù)時命中次數(shù) private int missCount;//查找數(shù)據(jù)時未命中次數(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; }
此時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會覆蓋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
會覆蓋 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ù),未命中此時+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值手動創(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ù)的,那么就跟我們手動創(chuàng)建的數(shù)據(jù)沖突了, //所以執(zhí)行撤消操作,重新把mapValue添加到緩存中,用mapValue去覆蓋createdValue map.put(key, mapValue); } else { //如果mapValue為空,說明之前的key值對應(yīng)的value確實為空,我們手動添加createdValue后, //需要重新計算緩存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)建了一個createdValue
,那么嘗試通過mapValue = map.put(key, createdValue)
把createdValue
添加到緩存中去,mapValue
是put()
方法的返回值,mapValue
代表的是key
之前對應(yīng)的值,如果mapValue
不為空,說明之前的key
對應(yīng)的是有數(shù)據(jù)的,那么就跟我們手動創(chuàng)建的數(shù)據(jù)沖突了,所以執(zhí)行撤消操作,重新把mapValue
添加到緩存中,用mapValue
去覆蓋createdValue
,最后再重新計算緩存大小。
移除數(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
,然后重新計算緩存大小,并返回刪除的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)實現(xiàn)代碼
這篇文章主要介紹了Android中監(jiān)聽軟鍵盤顯示狀態(tài)實現(xiàn)代碼,本文直接給出核心實現(xiàn)代碼,需要的朋友可以參考下2015-04-04關(guān)于Android Activity之間傳遞數(shù)據(jù)的6種方式
這篇文章主要介紹了關(guān)于Android Activity之間傳遞數(shù)據(jù)的6種方式,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-03-03Kotlin中空判斷與問號和感嘆號標(biāo)識符使用方法
最近使用kotlin重構(gòu)項目,遇到了一個小問題,在Java中,可能會遇到判斷某個對象是否為空,為空執(zhí)行一段邏輯,不為空執(zhí)行另外一段邏輯,下面這篇文章主要給大家介紹了關(guān)于Kotlin中空判斷與問號和感嘆號標(biāo)識符處理操作的相關(guān)資料,需要的朋友可以參考下2022-12-12Ubuntu中為Android系統(tǒng)上編寫Linux內(nèi)核驅(qū)動程序?qū)崿F(xiàn)方法
本文主要介紹在Ubuntu 上為Android系統(tǒng)編寫Linux內(nèi)核驅(qū)動程序, 這里對編寫驅(qū)動程序做了詳細(xì)的說明,對研究Android源碼和HAL都有巨大的幫助,有需要的小伙伴可以參考下2016-08-08Android 進(jìn)度條自動前進(jìn)效果的實現(xiàn)代碼
這篇文章主要介紹了Android 進(jìn)度條自動前進(jìn)效果,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07Android超詳細(xì)講解組件AdapterView的使用
AdapterView組件是一組重要的組件,AdapterView本身是一個抽象基類,它派生的子類在用法上十分相似,從AdapterView派生出的三個子類:AdsListView、AdsSpinner、AdapterViewAnimator,這3個子類依然是抽象的,實際運(yùn)用時需要它們的子類2022-03-03