Android緩存淺析

Android緩存淺析

By吳思博

1、引言

2、常見的幾種緩存算法

3、Android緩存的機(jī)制

4、LruCache的使用(內(nèi)存緩存)

5、云閱讀書籍解析緩存實(shí)現(xiàn)(內(nèi)存緩存實(shí)例,可跳過本節(jié))

6、DiskLruCache的使用(磁盤緩存)

1、引言

我們都聽過緩存,當(dāng)問你什么是緩存的時(shí)候,相信你能馬上給出一個(gè)完美的答案。?可是當(dāng)問你緩存是怎么構(gòu)建的,或者有一些怎樣緩存算法和框架?android中的緩存機(jī)制 ?,你可能會(huì)心神恍惚。

2、Android緩存的機(jī)制

最近在開發(fā)書籍解析章節(jié)中遇到了一些問題,經(jīng)過調(diào)試發(fā)現(xiàn)一部分是緩存的問題。云閱讀老版本中,解析書籍正文后緩存使用的是SoftReference,很容易被回收。Android中的緩存分為內(nèi)存緩存和文件緩存(磁盤緩存)。在早期常用的內(nèi)存緩存方式是軟引用(SoftReference)和弱引用(WeakReference),如大部分的使用方式:HashMap> ?imageCache;這種形式。從Android?2.3(Level 9)開始,垃圾回收器更傾向于回收SoftReference或WeakReference對(duì)象,這使得SoftReference和WeakReference變得不是那么實(shí)用有效。(到了Android?3.0(Level 11)之后,圖片數(shù)據(jù)Bitmap被放置到了內(nèi)存的堆區(qū)域,而堆區(qū)域的內(nèi)存是由GC管理的,開發(fā)者不需要進(jìn)行圖片資源的釋放工作,但這也使得圖片數(shù)據(jù)的釋放無法預(yù)知,增加了造成OOM的可能)。在Android3.1以后,Android推出了LruCache這個(gè)內(nèi)存緩存類,LruCache中的對(duì)象是強(qiáng)引用的。

3、常見的幾種緩存算法:

緩存算法有很多種,但是那種適合我們呢,下面來看看幾種常見的緩存算法。

3.1:FIFO(先進(jìn)先出)

先進(jìn)先出,原則簡單、且符合人們的慣性思維,具備公平性,并且實(shí)現(xiàn)起來簡單,直接使用數(shù)據(jù)結(jié)構(gòu)中的隊(duì)列即可實(shí)現(xiàn)。如果一個(gè)數(shù)據(jù)最先進(jìn)入緩存中,則應(yīng)該最早淘汰掉。也就是說,當(dāng)緩存滿的時(shí)候,應(yīng)當(dāng)把最先進(jìn)入緩存的數(shù)據(jù)給淘汰掉。實(shí)現(xiàn)方式:雙向鏈表(LinkedList)保存數(shù)據(jù),當(dāng)來了新的數(shù)據(jù)之后便添加到鏈表末尾,如果Cache存滿數(shù)據(jù),則把鏈表頭部數(shù)據(jù)刪除,然后把新的數(shù)據(jù)添加到鏈表末尾。在訪問數(shù)據(jù)的時(shí)候,如果在Cache中存在該數(shù)據(jù)的話,則返回對(duì)應(yīng)的value值;否則返回-1。

3.2:LRU(最少使用緩存算法)

把最近最少使用的緩存對(duì)象給淘汰。LRU總是需要去了解在什么時(shí)候,用了哪個(gè)緩存對(duì)象。瀏覽器就是使用了LRU作為緩存算法。新的對(duì)象會(huì)被放在緩存的頂部,當(dāng)緩存達(dá)到了容量極限,會(huì)把底部的對(duì)象淘汰。實(shí)現(xiàn)思路:把最新被訪問的緩存對(duì)象,放到緩存池的頂部。當(dāng)緩存達(dá)到了容量極限,會(huì)把底部的對(duì)象淘汰。所以,經(jīng)常被讀取的緩存對(duì)象就會(huì)一直呆在緩存池中。實(shí)現(xiàn)方式:使用array或者是linked list。LRU2和2Q,他們就是為了完善LRU而存在的。

Android 推薦:LruCache是Android 3.1所提供的一個(gè)緩存類,所以在Android中可以直接使用LruCache實(shí)現(xiàn)內(nèi)存緩存。而DisLruCache目前在Android 還不是Android SDK的一部分,但Android官方文檔推薦使用該算法來實(shí)現(xiàn)硬盤緩存。

3.3:LFU(最近使用頻率最少算法)

最近使用頻率最少算法;注意LFU和LRU算法的不同之處,LRU的淘汰規(guī)則是基于訪問時(shí)間,而LFU是基于訪問次數(shù)的。思想:當(dāng)緩存滿的時(shí)候,應(yīng)當(dāng)把訪問頻次最少的數(shù)據(jù)給淘汰掉。實(shí)現(xiàn):利用一個(gè)數(shù)組存儲(chǔ)數(shù)據(jù)項(xiàng),用HashMap存儲(chǔ)每個(gè)數(shù)據(jù)項(xiàng)在數(shù)組中對(duì)應(yīng)的位置,然后為每個(gè)數(shù)據(jù)項(xiàng)設(shè)計(jì)一個(gè)訪問頻次,當(dāng)數(shù)據(jù)項(xiàng)被命中時(shí),訪問頻次自增,在淘汰的時(shí)候淘汰訪問頻次最少的數(shù)據(jù)。在插入數(shù)據(jù)(插到數(shù)組的尾端)和訪問數(shù)據(jù)(數(shù)組隨機(jī)訪問)的時(shí)候都能達(dá)到O(1)的時(shí)間復(fù)雜度;淘汰數(shù)據(jù)的時(shí)候,通過選擇算法得到應(yīng)該淘汰的數(shù)據(jù)項(xiàng)在數(shù)組中的索引,并將該索引位置的內(nèi)容替換為新來的數(shù)據(jù)內(nèi)容即可,這樣的話,淘汰數(shù)據(jù)的操作時(shí)間復(fù)雜度為O(n)。

下面幾種算法都是上面的變種(可跳過):

3.4 LRU2(Least Recently Used 2):

Least Recently Used 2,又叫最近最少使用twice。LRU2把被兩次訪問過的對(duì)象放入緩存池,當(dāng)緩存池滿了之后,把有兩次最少使用的緩存對(duì)象淘汰。因?yàn)樾枰檶?duì)象2次,訪問負(fù)載就會(huì)隨著緩存池的增加而增加。如果把LRU2用在大容量的緩存池中,就會(huì)有問題。另外,LRU2還需要跟蹤不在緩存的對(duì)象,因?yàn)樗麄冞€沒有被第二次讀取。LRU2比LRU好,且是adoptive to access模式。

3.5 2Q(Two Queues):

2Q把被訪問的數(shù)據(jù)放到LRU的緩存中,如果這個(gè)對(duì)象再一次被訪問,2Q就把他轉(zhuǎn)移到第二個(gè)、更大的LRU緩存。2Q淘汰緩存對(duì)象是為了保持第一個(gè)緩存池是第二個(gè)緩存池的1/3。當(dāng)緩存的訪問負(fù)載是固定的時(shí)候,把LRU換成LRU2,就比增加緩存的容量更好。這種機(jī)制使得2Q比LRU2更好,2Q也是LRU家族中的一員,而且是adoptive to access模式。

3.6? ARC(Adaptive Replacement Cache):

ARC是介于LRU和LFU之間,為了提高效果,由2個(gè)LRU組成。第一個(gè)(L1)包含的條目是最近只被使用過一次的,而第二個(gè)LRU(L2)包含的是最近被使用過兩次的條目。(因此,L1放的是新的對(duì)象,而L2放的是常用的對(duì)象。)所以,才會(huì)認(rèn)為ARC是介于LRU和LFU之間的。

ARC被認(rèn)為是性能最好的緩存算法之一,能夠自調(diào),并且是低負(fù)載的。保存著歷史對(duì)象,這樣就可以記住那些被移除的對(duì)象。

…..? …..

4、LruCache的使用

LruCache是Android 3.1所提供的一個(gè)緩存類,所以在Android中可以直接使用LruCache實(shí)現(xiàn)內(nèi)存緩存。而DisLruCache目前在Android還不是Android SDK的一部分,但Android官方文檔推薦使用該算法來實(shí)現(xiàn)硬盤緩存。

LruCache的使用很簡單:

或者:

①設(shè)置LruCache緩存的大小,一般為當(dāng)前進(jìn)程可用容量的1/8。

②重寫sizeOf方法,計(jì)算出要緩存的每張圖片的大小。

注意:緩存的總?cè)萘亢兔總€(gè)緩存對(duì)象的大小所用單位要一致。

通過下面構(gòu)造函數(shù)來指定LinkedHashMap中雙向鏈表的結(jié)構(gòu)是訪問順序還是插入順序。

其中accessOrder設(shè)置為true則為訪問順序,為false,則為插入順序。

當(dāng)設(shè)置為true時(shí)

輸出結(jié)果:

0:0 3:3 4:4 5:5 6:6 1:1 2:2

即最近訪問的最后輸出,那么這就正好滿足的LRU緩存算法的思想。可見LruCache巧妙實(shí)現(xiàn),就是利用了LinkedHashMap的這種數(shù)據(jù)結(jié)構(gòu)。

下面我們?cè)贚ruCache源碼中具體看看,怎么應(yīng)用LinkedHashMap來實(shí)現(xiàn)緩存的添加,獲得和刪除的。

從LruCache的構(gòu)造函數(shù)中可以看到正是用了LinkedHashMap的訪問順序。

put()方法

可以看到put()方法并沒有什么難點(diǎn),重要的就是在添加過緩存對(duì)象后,調(diào)用trimToSize()方法,來判斷緩存是否已滿,如果滿了就要?jiǎng)h除近期最少使用的算法。

trimToSize()方法

trimToSize()方法不斷地刪除LinkedHashMap中隊(duì)尾的元素,即近期最少訪問的,直到緩存大小小于最大值。

當(dāng)調(diào)用LruCache的get()方法獲取集合中的緩存對(duì)象時(shí),就代表訪問了一次該元素,將會(huì)更新隊(duì)列,保持整個(gè)隊(duì)列是按照訪問順序排序。這個(gè)更新過程就是在LinkedHashMap中的get()方法中完成的。

get()方法:

其中LinkedHashMap的get()方法如下:

調(diào)用recordAccess()方法如下:

由此可見LruCache中維護(hù)了一個(gè)集合LinkedHashMap,該LinkedHashMap是以訪問順序排序的。當(dāng)調(diào)用put()方法時(shí),就會(huì)在結(jié)合中添加元素,并調(diào)用trimToSize()判斷緩存是否已滿,如果滿了就用LinkedHashMap的迭代器刪除隊(duì)尾元素,即近期最少訪問的元素。當(dāng)調(diào)用get()方法訪問緩存對(duì)象時(shí),就會(huì)調(diào)用LinkedHashMap的get()方法獲得對(duì)應(yīng)集合元素,同時(shí)會(huì)更新該元素到隊(duì)頭。

5、云閱讀書籍解析緩存(內(nèi)存緩存實(shí)例,可跳過本節(jié))

loadChapter()是章節(jié)解析函數(shù),首先從緩存中獲取章節(jié)解析,緩存中獲取章節(jié)解析不為null,直接返回緩存數(shù)據(jù),否則在mIGetChapterContentListener接口的onGetChapterContent()方法中獲取章節(jié)內(nèi)容,再進(jìn)行異步解析。

mIGetChapterContentListener的onGetChapterContent()方法中使用AsyncTask對(duì)章節(jié)進(jìn)行異步解析。

解析結(jié)束后,如果成功,并且當(dāng)前頁面為普通頁或者標(biāo)題頁則加入緩存。

緩存函數(shù):

具體實(shí)現(xiàn)在CacheManager類中。(如果緩存算法改變,只需要修改CacheManager類)

當(dāng)前CacheManager中使用LruCache算法實(shí)現(xiàn)。

6、DiskLruCache的使用

6.1創(chuàng)建

DISK_CACHE_SIZE緩存大小。

6.2添加

DishLruCache緩存添加的操作通過Eidtor完成,Editor為一個(gè)緩存對(duì)象的編輯對(duì)象。首先需要獲取圖片的url所對(duì)應(yīng)的key,根據(jù)key利用edit()來獲取Editor對(duì)象。若此時(shí)這個(gè)緩存正在被編輯,edit()會(huì)返回null。DiskLruCache不允許同時(shí)編輯同一個(gè)緩存對(duì)象。之所以把url轉(zhuǎn)換成key,因?yàn)閳D片的url中可能存在特殊字符,會(huì)影響使用,一般將url的md5值作為key

將url轉(zhuǎn)成key,利用這key值獲取Editor對(duì)象。若這個(gè)key的Editor對(duì)象不存在,edit()方法就創(chuàng)建一個(gè)新的出來。通過Editor對(duì)象可以獲取一個(gè)輸出流對(duì)象。DiskLruCache的open()方法中,一個(gè)節(jié)點(diǎn)只能有一個(gè)數(shù)據(jù),edit.newOutputStream(DISK_CACHE_INDEX)參數(shù)設(shè)置為0

這個(gè)文件輸出流,從網(wǎng)絡(luò)加載一個(gè)圖片后,通過這個(gè)OutputStream outputStream寫入文件系統(tǒng)。

上面的代碼并沒有將圖片寫入文件系統(tǒng),還需要通過Editor.commit()提交寫入操作,若寫入失敗,調(diào)用abort()方法,進(jìn)行回退整個(gè)操作。

這時(shí),圖片已經(jīng)正確寫入文件系統(tǒng),接下來的圖片獲取就不需要請(qǐng)求網(wǎng)絡(luò)

6.3緩存查找

查找過程,也需要將url轉(zhuǎn)換為key,然后通過DiskLruCache的get方法得到一個(gè)Snapshot對(duì)象,再通過Snapshot對(duì)象可得到緩存的文件輸入流,有了輸入流就可以得到Bitmap對(duì)象。為了避免oom,會(huì)使用ImageResizer進(jìn)行縮放。若直接對(duì)FileInputStream進(jìn)行操作,縮放會(huì)出現(xiàn)問題。FileInputStream是有序的文件流,兩次decodeStream調(diào)用會(huì)影響文件流的位置屬性。可以通過文件流得到其所對(duì)應(yīng)的文件描述符,利用BitmapFactory.decodeFileDescriptor()方法進(jìn)行縮放

在查找得到Bitmap后,把key,bitmap添加到內(nèi)存緩存中。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評(píng)論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評(píng)論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評(píng)論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,779評(píng)論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評(píng)論 1 330
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評(píng)論 0 291
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評(píng)論 1 338
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,515評(píng)論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,750評(píng)論 1 375
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,933評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評(píng)論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,492評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,703評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容