【源代碼】LruCache源代碼剖析
阿新 • • 發佈:2017-07-18
name 丟失 next() 兩個 sna eight get方法 util rem
上一篇分析了LinkedHashMap源代碼,這個Map集合除了擁有HashMap的大部分特性之外。還擁有鏈表的特點,即能夠保持遍歷順序與插入順序一致。
這篇文章分析的LruCache並非jdk中的類,而是來自安卓,熟悉安卓內存緩存的必定對這個類不陌生。 LinkedHashMap的初始化放在構造器中:
這裏將LinkedHashMap的accessOrder設置為true。
這種方法不斷循環刪除鏈表首部元素。也就是近期最少訪問的元素,直到容量不超過預先定義的最大值為止。 註:LruCache在android.util包中也有一個LruCache類,可是我發現這個類的trimToSize方法是錯誤的:
接下來是get方法:
get方法即依據key在LinkedHashMap中尋找相應的value,此方法也是線程安全的。
以上就是LruCache最重要的部分,以下再看下其它方法: remove:
sizeof:這種方法用於計算每一個條目的大小,子類必須得復寫這個類。
總結: 1.LruCache封裝了LinkedHashMap。提供了LRU緩存的功能; 2.LruCache通過trimToSize方法自己主動刪除近期最少訪問的鍵值對。 3.LruCache不同意空鍵值; 4.LruCache線程安全。 5.繼承LruCache時,必需要復寫sizeof方法。用於計算每一個條目的大小。
另外。當我們將accessOrder設置為true時。能夠使遍歷順序和訪問順序一致,其
內部雙向鏈表將會依照最近最少訪問到最近最多訪問的順序排列Entry對象,這能夠用來做緩存。這篇文章分析的LruCache並非jdk中的類,而是來自安卓,熟悉安卓內存緩存的必定對這個類不陌生。
LruCache內部維護的就是一個LinkedHashMap。 以下開始分析LruCache。 註:以下LruCache源代碼來自support.v4包。
首先是這個類的成員變量:
private final LinkedHashMap<K, V> map; /** Size of this cache in units. Not necessarily the number of elements. */ private int size;//當前大小 private int maxSize;//最大容量 private int putCount;//put次數 private int createCount;//create次數 private int evictionCount;//回收次數 private int hitCount;//命中次數 private int missCount;//丟失次數
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); }
這裏將LinkedHashMap的accessOrder設置為true。
接下來看兩個最重要的方法,put和get。
首先是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); if (previous != null) {//之前已經插入過同樣的key size -= safeSizeOf(key, previous);//那麽減去該entry的容量,由於發生覆蓋 } } if (previous != null) { entryRemoved(false, key, previous, value);//這種方法默認空實現 } trimToSize(maxSize);//若容量超過maxsize,將會刪除近期非常少訪問的entry return previous; }put方法無非就是調用LinkedHashMap的put方法。可是這裏在調用LinkedHashMap的put方法之前,推斷了key和value是否為空,也就是說LruCache不同意空鍵值。
除此之外,put操作被加鎖了,所以是線程安全的! 既然是緩存,那麽必定可以動態刪除一些不經常使用的鍵值對,這個工作是由trimToSize方法完畢的:
public void trimToSize(int maxSize) { while (true) {//不斷刪除linkedHashMap頭部entry,也就是近期最少訪問的條目。直到size小於最大容量 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 || map.isEmpty()) {//直到容量小於最大容量為止 break; } Map.Entry<K, V> toEvict = map.entrySet().iterator().next();//指向鏈表頭 key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key);//刪除最少訪問的entry size -= safeSizeOf(key, value); evictionCount++; } entryRemoved(true, key, value, null); } }
這種方法不斷循環刪除鏈表首部元素。也就是近期最少訪問的元素,直到容量不超過預先定義的最大值為止。 註:LruCache在android.util包中也有一個LruCache類,可是我發現這個類的trimToSize方法是錯誤的:
private 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; } Map.Entry<K, V> toEvict = null; for (Map.Entry<K, V> entry : map.entrySet()) { toEvict = entry; } if (toEvict == null) { break; } key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= safeSizeOf(key, value); evictionCount++; } entryRemoved(true, key, value, null); } }這裏的代碼將會循環刪除鏈表尾部,也就是近期訪問最多的元素,這是不對的!所以大家在做內存緩存的時候一定要註意,看trimToSize方法是否有問題。
接下來是get方法:
public final V get(K key) { if (key == null) {//不同意空鍵 throw new NullPointerException("key == null"); } V mapValue; synchronized (this) {//線程安全 mapValue = map.get(key);//調用LinkedHashMap的get方法 if (mapValue != null) { hitCount++;//命中次數加1 return mapValue;//返回value } missCount++;//未命中 } V createdValue = create(key);//默認返回為false if (createdValue == null) { return null; } synchronized (this) { createCount++;//假設創建成功,那麽create次數加1 mapValue = map.put(key, createdValue);//放到哈希表中 if (mapValue != null) { // There was a conflict so undo that last put map.put(key, mapValue); } else { size += safeSizeOf(key, createdValue); } } if (mapValue != null) { entryRemoved(false, key, createdValue, mapValue); return mapValue; } else { trimToSize(maxSize); return createdValue; } }
get方法即依據key在LinkedHashMap中尋找相應的value,此方法也是線程安全的。
以上就是LruCache最重要的部分,以下再看下其它方法: remove:
public final V remove(K key) { if (key == null) { throw new NullPointerException("key == null"); } V previous; synchronized (this) { previous = map.remove(key);//調用LinkedHashMap的remove方法 if (previous != null) { size -= safeSizeOf(key, previous); } } if (previous != null) { entryRemoved(false, key, previous, null); } return previous;//返回value }
sizeof:這種方法用於計算每一個條目的大小,子類必須得復寫這個類。
protected int sizeOf(K key, V value) {//用於計算每一個條目的大小 return 1; }snapshot方法,返回當前緩存中全部的條目集合
public synchronized final Map<K, V> snapshot() { return new LinkedHashMap<K, V>(map); }
總結: 1.LruCache封裝了LinkedHashMap。提供了LRU緩存的功能; 2.LruCache通過trimToSize方法自己主動刪除近期最少訪問的鍵值對。 3.LruCache不同意空鍵值; 4.LruCache線程安全。 5.繼承LruCache時,必需要復寫sizeof方法。用於計算每一個條目的大小。
【源代碼】LruCache源代碼剖析