談談LruCache演算法的底層實現原理及其內部原始碼

2021-09-07 03:42:41 字數 4048 閱讀 9494

我們在對資料進行操作的時候,為了避免流量或者效能的消耗,我們對於一些資料都會進行快取處理,而對資料的快取的要點不僅僅只有我們所熟悉的儲存快取和使用快取,還有刪除快取。對於新增和獲取快取很好理解,那麼為什麼還要對快取進行刪除吶?原因很簡單,因為我們的手機容量是有限的,如果我們拼命的寫入快取,那麼終有一天記憶體會滿導致程式奔潰,這顯然不是我們想要的結果,於是我們在寫入快取的時候並非無腦寫入,而是在寫入之前會對記憶體空間進行一次判斷,如果記憶體不足了,那麼就移除掉一些快取資料,從而達到記憶體不會爆掉的狀態。

根據上面所說的思想,android系統內部幫我們寫了乙個api——lrucache,來幫我們處理一些快取。

lrucache(least recently used)演算法的核心思想就是最近最少使用演算法。他在演算法的內部維護了乙個linkhashmap的鍊錶,通過put資料的時候判斷是否記憶體已經滿了,如果滿了,則將最近最少使用的資料給剔除掉,從而達到記憶體不會爆滿的狀態。這麼講可能有些抽象,我從網上找了一張圖來解釋這個演算法。

通過上面這張圖,我們可以看到,lrucache演算法內部其實是乙個佇列的形式在儲存資料,先進來的資料放在佇列的尾部,後進來的資料放在佇列頭部,如果要使用佇列中的資料,那麼使得之後將其又放在佇列的頭部,如果要儲存資料並且發現資料已經滿了,那麼便將佇列尾部的資料給剔除掉,從而達到我們使用快取的目的。這裡需要注意一點,隊尾儲存的資料就是我們最近最少使用的資料,也就是我們在記憶體滿的時候需要剔除掉的資料。

這裡有乙個疑問,為什麼lrucache內部原理的實現需要用到linkhashmap來儲存資料吶?

因為linkhashmap內部是乙個陣列加雙向鍊錶的形式來儲存資料,他能夠保證插入時候的資料和取出來的時候資料的順序的一致性。也就是說,我們以什麼樣的順序插入資料,就會以什麼樣的順序取出資料。並且更重要的一點是,當我們通過get方法獲取資料的時候,這個獲取的資料會從佇列中跑到佇列頭來,從而很好的滿足我們lrucache的演算法設計思想。

public static final void main(string args)

}輸出結果:

0:03:3

4:45:5

6:61:1

2:2可以看到,我們在插入的時候是0,1,2,3,4,5,6的形式插入的,然後呼叫get方法將1,2取出來。最後遍歷linkhashmap,發現最先出來的資料變成了2和1,其餘的資料都在這兩個資料之後取出來,這就達到了我們想要 的結果和目的。

lrucache演算法的使用

bitmap bitmap = null;

//獲取執行記憶體大小的八分之一

int memory = (int)runtime.getruntime().totalmemory() / 1024;

int cache = memory / 8;

bitmap = bitmapfactory.decoderesource(getresources(),r.mipmap.ic_launcher);

lrucachelrucache = new lrucache(cache)

};//將資料儲存進入快取

lrucache.put("cachebitmap",bitmap);

bitmap cachebitmap = lrucache.get("cachebitmap");

//在使用的時候判斷是否為空,因為有可能因為記憶體空間滿了而被剔除

if (cachebitmap != null)

我們在使用lrucache演算法的時候,首先會對其分配一下記憶體空間。一般情況下我們都是使用執行記憶體的八分之一作為記憶體空間來儲存資料。之後例項化lrucache,覆寫裡面的乙個方法sizeof,裡面返回的為你儲存進入快取的每乙個資料的大小。注意,此處的單位必須和你一開始分配的記憶體空間大小的單位保持一致。

這裡需要說乙個題外話,我們如何獲取乙個bitmap的記憶體大小?獲取bitmap記憶體大小的方式有兩種,一種為bitmap的高(bitmap.getheight(),或者可以說為行數) * bitmap所佔的位元組數(bitmap.getrowbyte())來獲取,另外一種為直接呼叫bitmap.getbytecount()來獲取。那麼這兩個有什麼區別吶?其實區別就是bitmap.getrowbyte()所支援的版本更低,而bitmap.getbytecount()記憶體的實現其實就是bitmap.getrowbyte() * bitmap.getheight();

public final int getbytecount()

初始化完成lrucache物件之後,我們通過put方法將需要進行快取的資料通過key和value的形式放入分配的記憶體中,之後需要使用的時候再通過get方法用key找到我們快取的物件。不過這裡需要注意一點,我們在獲取到我們的快取物件之後,不要急著去使用他,而是去先進行一次非空判斷,因為有可能因為記憶體空間不足而被剔除掉。

分析完了上面的內部原理和使用注意事項之後,我們再來通過原始碼來加深一下對lrucache演算法的理解。

我們先看下lrucache演算法的構造方法。

public lrucache(int maxsize)

this.maxsize = maxsize;

this.map = new linkedhashmap(0, 0.75f, true);

}從構造方法的原始碼我們可以看到,在這端**中我們主要做了兩件事。第一是判斷下傳遞來的最大分配記憶體大小是否小於零,如果小於零則丟擲異常,因為我們如果傳入乙個小於零的記憶體大小就沒有意義了。之後在構造方法記憶體就new了乙個linkhashmap集合,從而得知lrucache內部實現原理果然是基於linkhashmap來實現的。

之後我們再來看下儲存快取的put()方法。

public final v put(k key, v value)

v previous;

synchronized (this)

}if (previous != null)

trimtosize(maxsize);

return previous;

}從**中我們可以看到,這個put方法內部其實沒有做什麼很特別的操作,就是對資料進行了一次插入操作。但是我們注意到最後的倒數第三行有乙個trimtosize()方法,那麼這個方法是做什麼用的吶?我們點進去看下。

public void trimtosize(int maxsize)

if (size <= maxsize)

map.entrytoevict = map.eldest();

if (toevict == null)

key = toevict.getkey();

value = toevict.getvalue();

map.remove(key);

size -= safesizeof(key, value);

evictioncount++;

}entryremoved(true, key, value, null);}}

我們可以看到,這個方法原來就是對記憶體做了一次判斷,如果發現記憶體已經滿了,那麼就呼叫map.eldest()方法獲取到最後的資料,之後呼叫map.remove(key)方法,將這個最近最少使用的資料給剔除掉,從而達到我們記憶體不炸掉的目的。

我們再來看看get()方法。

public final v get(k key)

v mapvalue;

synchronized (this)

misscount++;

}get方法看起來就是很常規的操作了,就是通過key來查詢value的操作,我們再來看看linkhashmap的中get方法。

public v get(object key)

呼叫recordaccess()方法如下:

void recordaccess(hashmapm)

}由此可見lrucache中維護了乙個集合linkedhashmap,該linkedhashmap是以訪問順序排序的。當呼叫put()方法時,就會在結合中新增元素,並呼叫trimtosize()判斷快取是否已滿,如果滿了就用linkedhashmap的迭代器刪除隊尾元素,即最近最少訪問的元素。當呼叫get()方法訪問快取物件時,就會呼叫linkedhashmap的get()方法獲得對應集合元素,同時會更新該元素到隊頭。

談談LruCache演算法的底層實現原理及其內部原始碼

我們在對資料進行操作的時候,為了避免流量或者效能的消耗,我們對於一些資料都會進行快取處理,而對資料的快取的要點不僅僅只有我們所熟悉的儲存快取和使用快取,還有刪除快取。對於新增和獲取快取很好理解,那麼為什麼還要對快取進行刪除吶?原因很簡單,因為我們的手機容量是有限的,如果我們拼命的寫入快取,那麼終有一...

LRu Cache演算法原理

lru cache演算法原理 1.新資料插入到鍊錶頭部 2.每當快取命中 即快取資料被訪問 則將資料移到鍊錶頭部 3.當鍊表滿的時候,將鍊錶尾部的資料丟棄。分析 命中率 當存在熱點資料時,lru的效率很好,但偶發性的 週期性的批量操作會導致lru命中率急劇下降,快取汙染情況比較嚴重。複雜度 實現簡單...

LRUCache演算法的簡單實現

lru是least recently used的縮寫,意為最近最少使用演算法。lrucache是一種常用的快取替換演算法,根據使用率淘汰資料,即使用率最小的會被淘汰,通常會用乙個雙向鍊錶來實現,在這個雙向鍊錶中,如果乙個cache被命中,則將這個資料移動到鍊錶的頭部,而不經常使用的cache就會逐漸...