LruCache是Android中的一個緩存工具類,它採用了一種最近最少使用算法,能夠將一些對象進行內存緩存,當緩存滿後,會優先刪除近期最少使用的對象。LruCache在實際開發中是使用率很是高的一個工具類,許多著名的圖片加載,網絡請求等框架內部都是使用的LruCache對象進行數據緩存,所以咱們有必要了解LruCache內部的工做原理。java
LruCache自己是一個泛型類,使用起來也很是簡單,若是咱們想要使用LruCache對象,首先要實例化一個LruCache對象,並重寫它的sizeOf方法,咱們以緩存Bitmap對象爲例:算法
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int maxSize = maxMemory / 8;
LruCache<String, Bitmap> lruCache = new LruCache<String, Bitmap>(maxSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
複製代碼
LruCache的構造方法須要傳入一個maxSize參數,這個參數表明能夠緩存的最大值。而sizeOf方法用來計算要緩存的對象的大小。緩存
若是咱們想要緩存一個對象,只須要調用LruCache對象的put(K key, V value)方法便可,而當咱們想要拿取一個緩存時,則須要調用get(K key)方法:安全
lruCache.put(key, bitmap);
Bitmap cache = lruCache.get(key);
複製代碼
咱們經過分析LruCache的源碼來分析LruCache的工做原理。首先看LruCache的構造方法:markdown
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);
}
複製代碼
咱們能夠看到,在構造方法中首先將maxSize參數保存在了一個成員變量中,而後初始化了一個LinkedhashMap對象。這個LinkedHashMap其實就是LruCache的核心部分,使用LruCache緩存的對象實際上是存儲在這個LinkedHashMap中的。網絡
LinkedHashMap是HashMap的一個子類,與HashMap不一樣的是,LinkedHashMap可以記住每條記錄的插入順序。LinkedHashMap類有許多重載的構造方法,而在LruCache中使用的是LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder)這個構造方法,其中最關鍵的就是accessOrder這個參數,它表明着LinkedhashMap中每條記錄的排序規則。當accessOrder爲false時,LinkedHashMap是按照插入順序來對每條記錄進行排序的,而當accessOrder爲true時,LinkedHashMap則會採用每條記錄的訪問順序來進行排序。框架
舉個例子:ide
Map<Integer, String> hashMap = new HashMap<>();
hashMap.put(3, "第一條");
hashMap.put(2, "第二條");
hashMap.put(1, "第三條");
System.out.println("HashMap:\n" + hashMap);
//採用這個無參的構造方法建立LinkedHashMap時,accessOrder默認爲false
Map<Integer, String> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put(3, "第一條");
linkedHashMap.put(2, "第二條");
linkedHashMap.put(1, "第三條");
System.out.println("\nLinkedHashMap:\n" + linkedHashMap);
Map<Integer, String> linkedHashMap2 = new LinkedHashMap<>(0, 0.75f, true);
linkedHashMap2.put(3, "第一條");
linkedHashMap2.put(2, "第二條");
linkedHashMap2.put(1, "第三條");
linkedHashMap2.get(2);//在這裏訪問了一下"第二條"
System.out.println("\nLinkedHashMap2:\n" + linkedHashMap2);
複製代碼
輸出結果:工具
HashMap:
{1=第三條, 2=第二條, 3=第一條}
LinkedHashMap:
{3=第一條, 2=第二條, 1=第三條}
LinkedHashMap2:
{3=第一條, 1=第三條, 2=第二條}
複製代碼
能夠看到默認狀況下LinkedHashMap是按照順序來存儲每條記錄的,先插入的在前,後插入的在後,而當accessOrder爲true時,最近訪問的記錄會被排到後面。LruCache就是根據LinkedHashMap這個特性來判斷哪些緩存數據須要被優先清除的。this
LruCache使用put方法來插入一個緩存數據,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);//更新已經使用的緩存的大小
previous = map.put(key, value); //將數據插入到LinkedHashMap中
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
複製代碼
put方法的源碼很是好理解,首先盤點key和value是否爲null,若是爲null就直接拋出異常了。而後使用了synchronized關鍵字來保證線程安全。size變量表明當前已經使用的緩存的大小,使用sizeOf方法來計算本次提交的緩存數據的大小,並與size相加來更新已經使用的緩存大小。以後將本次提交的數據插入到LinkedHashMap裏。
在調用LinkedHashMap的put方法時,若是LinkedHashMap中已經存在以這個"Key"爲鍵的數據,則會用新插入的數據覆蓋舊的數據,而後將舊的數據返回,返回的舊數據被保存在了previous這個變量內,若是LinkedHashMap中不存在以這個"Key"爲鍵的數據則返回null。所以,當previous不爲null時,說明有舊數據被覆蓋了,咱們要減去這個舊數據所佔用的空間大小。
entryRemoved方法會在某個緩存數據被移除時被調用,默認狀況下該方法是個空方法。咱們能夠根據需求來重寫這個方法,在緩存對象要被清除時作一些處理。
最後調用了trimToSize方法來計算當前緩存數據的大小是否超過了緩存容許的最大值的限制,trimToSize方法的源碼以下:
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中止循環
break;
}
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);
}
}
複製代碼
當已用緩存大小超過最大緩存時,經過map.eldest()來獲取訪問時間最先的那個元素,而後將它從LinkedHashMap中刪除,並從新計算已用緩存的大小,若是已用緩存的大小仍然大於最大緩存大小,則進行下一次循環繼續進行刪除,不然打斷循環。
LruCache對象經過get方法來獲取一個緩存。get方法的源碼以下:
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
mapValue = map.get(key);//從LinkedHashMap中拿取緩存對象
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
V createdValue = create(key);//經過create方法試圖建立一個對象
if (createdValue == null) {
return null;
}
//如下代碼與put方法相似
synchronized (this) {
createCount++;
mapValue = map.put(key, createdValue);
if (mapValue != null) {
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
複製代碼
put方法的代碼略長,但理解起來其實很是簡單。依舊是宣判斷key是否爲null。以後經過map.get(key)來從LinkedHashMap中獲取緩存的數據。若是獲取的數據不爲null,則直接將其返回,而且LinkedHashMap會自動將這個緩存對象排列到最後面。
若是map.get(key)獲取的值爲空,則會試圖調用create方法來建立一個對象,create默認狀況下直接返回null。咱們能夠根據需求重寫create方法來實現本身想要的結果。若是create成功的建立了一個對象,LruCache則會將這個對象添加到LinkedHashMap中,並返回該對象。
remove方法用來移除一個緩存對象,源碼以下:
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;
}
複製代碼
在理解了put和get方法後,remove的源碼讀起來就毫無壓力了。經過map.remove(key)來直接從LinkedHashMap中移除該對象,並更新size的值,而後調用entryRemoved方法進行處理。代碼很簡單,就再也不多說了。