文章目錄
- Android LruCache源碼分析
- 概述
- LruCache和LinkedHashMap關系
- 源碼分析
- 屬性
- 寫入數據
- 讀取數據
- 刪除緩存
Android LruCache源碼分析
概述
LruCache(Least Recently Used Cache,最近最少使用緩存)是 Android 中的一種緩存機制。
根據數據的使用頻率淘汰減少使用的數據,當需要緩存新數據時,如果緩存已滿,LruCache 會淘汰最近最少使用的數據,騰出空間給新數據。
LruCache和LinkedHashMap關系
LruCache 內部使用的是 LinkedHashMap(鏈式哈希表),這是因為 LinkedHashMap 的構造函數里有個布爾參數 accessOrder,當它為 true 時,LinkedHashMap 會以訪問順序的方式排列元素,如下:
Map<Integer, Integer> map = new LinkedHashMap<>(5, 0.75F, true);
map.put(1, 1);
map.put(2, 2);
map.put(3, 3);
map.put(4, 4);
map.put(5, 5);
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {System.out.println(entry.getValue());
}/** 1* 2* 3* 4* 5*/
// 訪問2個元素
map.get(3);
map.get(4);
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {System.out.println(entry.getValue());
}/** 1* 2* 5* 3* 4*/
最近訪問的2個元素被移動到尾部,LruCache 也是從尾部訪問數據,在表頭刪除數據。
源碼分析
屬性
public class LruCache<K, V> {private final LinkedHashMap<K, V> map; // 當前緩存大小private int size;// 最大緩存容量private int maxSize;// 寫入計數private int putCount;// 創建計數private int createCount;// 淘汰計數private int evictionCount;// 緩存命中計數private int hitCount;// 緩存未命計數private int missCount;
}
寫入數據
public final V put(K key, V value) {// 如果值為null,則拋出異常if (key == null || value == null) {throw new NullPointerException("key == null || value == null");}V previous;// 加鎖,線程安全synchronized (this) {// 寫入計數putCount++;// 通過sizeOf()計算當前項的大小,并累加已有緩存大小size += safeSizeOf(key, value);// 寫入操作previous = map.put(key, value);// 如果previous為null表示為新增數據,如果previous不為null表示為修改數據if (previous != null) {// 修改數據需要將size恢復到以前的大小size -= safeSizeOf(key, previous);}}// 回調entryRemoved()方法if (previous != null) {entryRemoved(false, key, previous, value);}// 調整緩存大小trimToSize(maxSize);return previous;
}// 調整緩存大小
public void trimToSize(int maxSize) {// 死循環while (true) {K key;V value;synchronized (this) {// 緩存未滿,直接返回if (size <= maxSize || map.isEmpty()) {break;}// 緩存已滿情況// 從表頭遍歷,獲取元素Map.Entry<K, V> toEvict = map.entrySet().iterator().next();key = toEvict.getKey();value = toEvict.getValue();// 刪除元素map.remove(key);// 減少刪除元素的緩存size -= safeSizeOf(key, value);// 刪除計數evictionCount++;}// 回調entryRemoved()方法entryRemoved(true, key, value, null);}
}
- 插入元素,并增加已緩存的大小。
- 調用 trimToSize() 方法,調整緩存大小。
讀取數據
public final V get(@NonNull K key) {if (key == null) {throw new NullPointerException("key == null");}V mapValue;synchronized (this) {// 獲取元素,LinkedHashMap會將這個元素移動到表尾mapValue = map.get(key);if (mapValue != null) {hitCount++;return mapValue;}missCount++;}// 沒有元素時,會回調create()方法V createdValue = create(key);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;}
}
- 最終調用 LinkedHashMap#get() 方法,因為accessOrder為true ,因此元素會移動到表尾。
- 如果沒有獲取到元素時,會調用 create() 方法創建元素,接著執行put()流程。
刪除緩存
public final V remove(@NonNull K key) {if (key == null) {throw new NullPointerException("key == null");}V previous;synchronized (this) {// 調用LinkedHashMap#remove()方法刪除元素previous = map.remove(key);if (previous != null) {size -= safeSizeOf(key, previous);}}if (previous != null) {entryRemoved(false, key, previous, null);}return previous;
}
- 調用 LinkedHashMap#remove() 方法刪除元素。