LRU算法簡介
LRU 全稱是 least recently used,意爲“最近最少使用”,說白了就是一種淘汰算法,當有新的元素插入進來的時候,我們的使用空間又有限的時候,就需要淘汰舊的元素,這時候就會選擇淘汰最近最少使用的元素。
應用場景
在app開發中,假設有一個頁面列表需要從網絡獲取大量圖片,在頁面切換、滑動中,同一張圖片不可能每次都去網絡獲取,這樣對內存、性能和用戶體驗都是一個考驗,最好的做法是設置本地文件緩存和內存緩存,存儲從網絡取得的數據。
但本地文件緩存空間並非是無限大,容量越大讀取效率越低,可設置一個折中緩存容量比如10M,如果緩存已滿,我們需要採用合適的替換策略換掉一個已有的數據對象,並替之已一個新的數據對象;內存緩存作爲最先被讀取的數據,應該存儲那些經常使用的數據對象,且內存容量有限,內存緩存的容量也應該限定。
實現原理
Android中有一個LRU算法實現的集合LruCache。
public class LruCache<K, V> {
private final LinkedHashMap<K, V> map;
}
可以看出LruCache內部是使用LinkedHashMap存儲數據的。其實LruCache主要依靠LinkedHashMap本身的LRU實現來實現的。LruCache只是進行了另一次封裝。
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
// 這裏指定了該集合的最大容量,一旦集合容量大於該容量則會調用trimToSize方法來減少容量。
this.maxSize = maxSize;
// 這裏創建了LinkedHashMap並且第三個參數指定爲true.該參數爲true時LinkedHashMap開啓LRU算法。
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
在使用LruCache時要重寫sizeOf方法,來指定成員的實際容量。否則默認返回1
protected int sizeOf(K key, V value) {
return 1;
}
LruCache的一個回調函數本身沒有實現,可重新。在新增或移除變量時會被調用。第一個參數爲true時是移除變量,false時是新增變量。
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
接下來簡單說下LruCache的存放方法。
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) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
// 新增時會調用的回調函數,本身沒有具體實現需要使用時要自己重寫
entryRemoved(false, key, previous, value);
}
// 這裏是重點,當插入結束後會檢查內存是否超標
trimToSize(maxSize);
return previous;
}
/**
*簡單來說就是會無限循環的來檢測內存容量
*如果內存容量大於maxSize,則會移除最近最久使用的成員。
*然後繼續循環,直到內存容量小於maxSize或者集合爲null循環終止
**/
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;
}
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);
}
}
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
// 如果值存在則直接返回值,這裏會依賴LinkedHashMap的LRU機制。
V mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
// 這裏會調用create方法,這個方法本身默認返回null,即如果用戶不重寫get方法會返回null。
// 根據實際需求來編寫。如果創建了新的對象則會存放進集合中。
V createdValue = create(key);
if (createdValue == null) {
return null;
}
synchronized (this) {
createCount++;
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;
}
}
資料源自互聯網,經綜合整理而成,如有侵犯,請聯繫我。