某年某月某日,糖葫蘆同窗在掘金app上看了幾篇文章,偶然看到了一篇熟悉的詞LRU算法,腦海裏就想這不是常常說的嘛,就那麼回事,當天晚上睡覺,LRU算法是啥來着,好像是什麼最近最少使用的,白天在地鐵上看的文章也很多,可是到晚上想一想好像啥也沒記住,就記得LRU算法,我發現人大多數是這樣的啊,對於本身熟悉的部分呢還能記着點,不熟悉或者不會的可能真的是看過就忘啊~既然這樣還不如先把熟悉的弄明白。java
次日來到公司,我覺着仍是有必要看一下這個LRU的源碼,究竟是怎麼回事,嗯,糖葫蘆同窗刷刷得看,下面咱們將進入正題,請戴眼鏡的同窗把眼鏡擦一擦,哈哈哈算法
先看源碼,再用具體的demo加以驗證,咱們先看一下這個LruCache這個類的大體結構和方法,以下圖所示: 緩存
這又是 get(K),put(K,V), remove(K) 的方法的 給人的感受就像是一個Map的集合嘛,又有Key ,又有value 的,再看下具體的代碼:app
public class LruCache<K, V> {
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;
private int createCount;
private int evictionCount;
private int hitCount;
private int missCount;
/** * @param maxSize for caches that do not override {@link #sizeOf}, this is * the maximum number of entries in the cache. For all other caches, * this is the maximum sum of the sizes of the entries in this cache. */
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);
}
複製代碼
看到開頭,咱們就明白了,哦原來這個LruCache類中維護一個LinkedHashMap的一個集合,緩存咱們這個對象,並且構造方法裏須要咱們傳入一個maxSize
的一個值,根據上面的註釋咱們就明白了這個就是咱們LruCache緩存對象的最大數目。ide
根據慣性思惟,咱們能夠認爲,在put
新的緩存對象的時候,根據咱們設定的最大值remove
集合裏的某些緩存對象,進而添加新的緩存對象。佈局
根據咱們的分析,咱們有必要去看一下這個put
方法的源碼:this
/** * Caches {@code value} for {@code key}. The value is moved to the head of * the queue. * * @return the previous value mapped by {@code key}. */
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;
}
複製代碼
代碼量也不是特別多,咱們看下這個,在這個synchronized
同步代碼塊裏,咱們看到這個 size
,是對put進來緩存對象個數的累加,而後調用集合的map.put
方法,返回一個對象 previous
,就是判斷這個集合中是否添加了這個緩存對象,若是不爲null,就對size
減回去。spa
最後又調用一個 trimToSize(maxSize)
方法,上面都是對添加一些邏輯的處理,那麼不可能無限制添加啊,確定有移除操做,那麼咱們推測這個邏輯可能在這個trimToSize(maxSize)
裏處理。3d
源碼以下:code
/** * Remove the eldest entries until the total of remaining entries is at or * below the requested size. * * @param maxSize the maximum size of the cache before returning. May be -1 * to evict even 0-sized elements. */
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!");
}
//只要當前size<= maxSize 就結束循環
if (size <= maxSize || map.isEmpty()) {
break;
}
// 獲取這個對象,而後從map中移除掉,保證size<=maxSize
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
複製代碼
註釋:Remove the eldest entries until the total of remaining entries is at or below the requested size
大概意思是說:清除時間最久的對象直到剩餘緩存對象的大小小於設置的大小。沒錯是咱們想找的。
這裏說明一下: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);
}
複製代碼
這樣LruCache的核心方法 trimToSize
方法咱們就說完了,接下來我將經過實例再次驗證下:
假設咱們設置maxSize 爲2,佈局裏顯示3個imageView,分別表明3張咱們要顯示的圖片,咱們添加3張圖片,看看會不會顯示3張?
xml佈局顯示以下(代碼就不貼了,很簡單):
activity代碼以下:
public final int MAX_SIZE = 2;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_lru);
ImageView iv1 = (ImageView) findViewById(R.id.iv1);
ImageView iv2 = (ImageView) findViewById(R.id.iv2);
ImageView iv3 = (ImageView) findViewById(R.id.iv3);
Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(),R.drawable.bg);
Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(),R.drawable.header_img);
Bitmap bitmap3 = BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher);
LruCache<String,Bitmap> lruCache = new LruCache<>(MAX_SIZE);
lruCache.put("1",bitmap1);
lruCache.put("2",bitmap2);
lruCache.put("3",bitmap3);
Bitmap bitmap = lruCache.get("1");
iv1.setImageBitmap(bitmap);
Bitmap b2 = lruCache.get("2");
iv2.setImageBitmap(b2);
Bitmap b3 = lruCache.get("3");
iv3.setImageBitmap(b3);
}
複製代碼
圖:
咱們能夠先嚐試分析一下:由於咱們設置的MaxSize 是2 ,那麼在put第三個Bitmap的時候,在trimToSize
方法中,發現這個size是3 ,maxSize 是2,會繼續向下執行,不會break,結合下面代碼看下
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!");
}
//第一次循環:此時 size 是3,maxSize 是 2
//第二次循環,此時 size 是 2 ,maxSize 是 2 ,知足條件,break,結束循環
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 = 2,減去移除的元素
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
複製代碼
這個 safeSizeOf
是調用sizeOf
方法。
那麼也就是說,咱們在put
第三個bitmap
的時候,LruCache
會自動幫咱們移除掉第一個緩存對象,由於第一個最早添加進去,時間也最長,固然後添加的bitmap
就是新的,最近的,那麼咱們推斷這個iv1
是顯示不出圖片的,由於被移除掉了,其它剩餘兩個能夠顯示,分析就到這裏,看下運行結果是否是跟咱們分析的同樣:
哇!真的跟咱們想的同樣耶,證實咱們想的是對的。這裏咱們思考一下就是爲何LruCache
使用了這個LinkedHashMap
,爲何LinkedHashMap
的創造方法跟咱們平時建立的不太同樣,源碼是這樣的:
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);
}
複製代碼
這裏說一下評論裏
藏地情人
評論是:new LinkedHashMap<K, V>(0, 0.75f, true)
這句代碼表示,初始容量爲零,0.75
是加載因子,表示容量達到最大容量的75%
的時候會把內存增長一半。最後這個參數相當重要。表示訪問元素的排序方式,true
表示按照訪問順序排序,false
表示按照插入的順序排序。這個設置爲true
的時候,若是對一個元素進行了操做(put、get)
,就會把那個元素放到集合的最後。
確實也是這樣的,咱們看下LinkedHashMap
的源碼:
/** * Constructs an empty <tt>LinkedHashMap</tt> instance with the * specified initial capacity, load factor and ordering mode. * * @param initialCapacity the initial capacity * @param loadFactor the load factor * @param accessOrder the ordering mode - <tt>true</tt> for * access-order, <tt>false</tt> for insertion-order * @throws IllegalArgumentException if the initial capacity is negative * or the load factor is nonpositive */
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
複製代碼
裏面這個assessOrder
註釋裏也說的很明白:the ordering mode - <tt>true</tt> for * access-order, <tt>false</tt> for insertion-order
-> true
呢就表示會排序,false
就表明按照插入的順序。默認不傳就是 false
,並且咱們每次 get(K) put(K,V)
的時候 會根據這個變量調整元素在集合裏的位置。而這麼作的目的也只有一個:保留最近使用的緩存對象,舉個例子說明一下:
咱們向這個集合裏添加了三種元素
LruCache<String, Bitmap> lruCache = new LruCache<>(MAX_SIZE);(MAX_SIZE=2)
lruCache.put("1", bitmap1);
lruCache.put("2", bitmap2);
lruCache.put("3", bitmap3);
複製代碼
此時它們在集合裏的順序是這樣的:
那好比說咱們在put
3 元素以前,使用了1元素,就是調用了get("1")
方法,咱們知道LinkedHashMap就會改變鏈表裏元素的存儲順序,代碼是這樣的:
lruCache.put("1", bitmap1);
lruCache.put("2", bitmap2);
lruCache.get("1");
lruCache.put("3", bitmap3);
複製代碼
那麼此時對應鏈表裏的順序就是:
複製代碼
當咱們再調用顯示的時候,循環遍歷就會優先把第一個位置的key = "2"
的緩存對象移除掉,保證了最近使用的原則,固然了由於把這個max_size = 2
因此在咱們執行lruCache.put("3", bitmap3);
時,集合最終會變成這樣:
集合裏只剩下 1 ,3
對應的緩存對象。
至此,LruCache就說完了,若是看完的你有不明白的地方能夠留言,一塊兒討論下~