安卓開發學習之LruCache原始碼閱讀
背景
LruCache是最近最久未使用的快取,是安卓系統裡常見的快取策略之一。
原始碼閱讀
LruCache類裡定義的屬性如下
private final LinkedHashMap<K, V> map; // 記憶體物件,雜湊連結串列 private int size; // 目前使用的記憶體數 private int maxSize; // 最大記憶體數 private int putCount; // 新增到map佇列的運算元 private int createCount; // 新增的空白記憶體的數量 private int evictionCount; // 被lru演算法刪除(而不是使用者手動呼叫刪除)的記憶體數量 private int hitCount; // 根據鍵找值時找到的次數 private int missCount; // 找不到值的次數
原來就是封裝了一個雜湊連結串列,然後記錄各種數量。於是我順著構造方法、增加、查詢、刪除、清空以及其他方法的順序進行原始碼閱讀
構造方法
構造方法程式碼如下,傳入了一個maxSize引數
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); }
儲存最大記憶體數量,然後例項化map,但是map初始長度是0,增長因子是0.75(也就是當新增一個元素進去時,發現當前連結串列長度達到或超過連結串列最大長度的75%時,會先擴容到原來的兩倍,再新增新元素),第三個引數表示對映連結串列的排序方式,true表示按照訪問順序排列,false表示按照插入順序排序,這裡要實現最近最久未使用,自然要按照訪問順序排列對映連結串列,把不常用的放到連結串列頭部,常用的放到連結串列尾部。
新增方法
給快取裡新增元素的方法程式碼如下,實際是對對映連結串列的操作
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++; // 移入數量+1 size += safeSizeOf(key, value); // size++ previous = map.put(key, value); // 把新的鍵值對插入,返回鍵對應的老值 if (previous != null) { size -= safeSizeOf(key, previous); // 如果以前有值,size不應該變化 } } if (previous != null) { // 回撥,空實現 entryRemoved(false, key, previous, value); } trimToSize(maxSize); // 保證size不大於maxSize return previous; }
這個方法前面的都比較容易理解,而最後的trimToSize()方法則是用來剔除連結串列首部那些不常用的元素的,剔除的幅度是保證當前對映連結串列長度不大於傳入的引數。我們看一下trimToSize()方法
public void trimToSize(int maxSize) {
// 從頭結點開始移除,直到size不大於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;
}
Map.Entry<K, V> toEvict = map.eldest(); // 直接返回頭結點
if (toEvict == null) {
break; // 頭結點是空,連結串列就是空,退出迴圈,結束方法
}
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value); // size--
evictionCount++; // 移除數量++
}
entryRemoved(true, key, value, null); // 空實現
}
}
獲取元素的方法
獲取元素的方法是get()方法,程式碼如下
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
mapValue = map.get(key); // 根據鍵取值
if (mapValue != null) { // 取到的話,直接返回
hitCount++; // 找到數++
return mapValue;
}
missCount++; // 否則,丟失數++,進入下面的處理
}
V createdValue = create(key); // 預設返回null
if (createdValue == null) {
return null; // 返回null
}
// 如果某個類覆寫了create()方法,讓它返回不是null
synchronized (this) {
createCount++; // 創造數+1
mapValue = map.put(key, createdValue); // 先嚐試把新值插進去,獲得老值
// 如果老值不為空,就不採用新值,還把老值插回去,否則size++
if (mapValue != null) {
// There was a conflict so undo that last put
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue); // size++
}
}
if (mapValue != null) { // 老值不是null,返回
entryRemoved(false, key, createdValue, mapValue); // 空實現
return mapValue;
} else { // 否則就是插入了新值,返回新值
trimToSize(maxSize); // 同時執行lru演算法,清除頭結點
return createdValue;
}
}
刪除方法
刪除方法是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); // size--
}
}
if (previous != null) {
entryRemoved(false, key, previous, null); // 空實現
}
return previous;
}
清空方法
清空方法是evictAll()方法,程式碼如下
public final void evictAll() {
trimToSize(-1); // -1 will evict 0-sized elements
}
給trimToSize()傳入的引數是-1,那麼trimToSize()方法會從連結串列首位開始清除,直到對映連結串列的大小是0為止,當然就清空了
其他方法
重置大小
resize()方法,程式碼如下
public void resize(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
synchronized (this) {
this.maxSize = maxSize;
}
trimToSize(maxSize);
}
其實就是改變一下maxSize,然後呼叫trimToSize()方法執行lru以適應新的容量
快照方法
snapShot()方法,程式碼如下
public synchronized final Map<K, V> snapshot() {
return new LinkedHashMap<K, V>(map);
}
就是獲取一個map的副本出去
結語
安卓裡的LruCache類就是這樣,內部核心是維護了一個對映連結串列,以及實現了一個trimToSize()方法,這個方法也是lru的實現。
以上原始碼來自Android8.0,sdk26