Android緩存淺析

Android緩存淺析

By吳思博

1藕溅、引言

2询吴、常見(jiàn)的幾種緩存算法

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

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

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

6绵估、DiskLruCache的使用(磁盤(pán)緩存)

1、引言

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

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

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

3、常見(jiàn)的幾種緩存算法:

緩存算法有很多種务漩,但是那種適合我們呢拄衰,下面來(lái)看看幾種常見(jiàn)的緩存算法。

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

先進(jìn)先出饵骨,原則簡(jiǎn)單翘悉、且符合人們的慣性思維,具備公平性居触,并且實(shí)現(xiàn)起來(lái)簡(jiǎn)單妖混,直接使用數(shù)據(jù)結(jié)構(gòu)中的隊(duì)列即可實(shí)現(xiàn)。如果一個(gè)數(shù)據(jù)最先進(jìn)入緩存中轮洋,則應(yīng)該最早淘汰掉制市。也就是說(shuō),當(dāng)緩存滿(mǎn)的時(shí)候弊予,應(yīng)當(dāng)把最先進(jìn)入緩存的數(shù)據(jù)給淘汰掉祥楣。實(shí)現(xiàn)方式:雙向鏈表(LinkedList)保存數(shù)據(jù),當(dāng)來(lái)了新的數(shù)據(jù)之后便添加到鏈表末尾汉柒,如果Cache存滿(mǎn)數(shù)據(jù)误褪,則把鏈表頭部數(shù)據(jù)刪除,然后把新的數(shù)據(jù)添加到鏈表末尾竭翠。在訪(fǎng)問(wèn)數(shù)據(jù)的時(shí)候振坚,如果在Cache中存在該數(shù)據(jù)的話(huà),則返回對(duì)應(yīng)的value值斋扰;否則返回-1渡八。

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

把最近最少使用的緩存對(duì)象給淘汰。LRU總是需要去了解在什么時(shí)候传货,用了哪個(gè)緩存對(duì)象屎鳍。瀏覽器就是使用了LRU作為緩存算法。新的對(duì)象會(huì)被放在緩存的頂部问裕,當(dāng)緩存達(dá)到了容量極限逮壁,會(huì)把底部的對(duì)象淘汰。實(shí)現(xiàn)思路:把最新被訪(fǎng)問(wè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è)緩存類(lèi)朵诫,所以在Android中可以直接使用LruCache實(shí)現(xiàn)內(nèi)存緩存。而DisLruCache目前在Android 還不是Android SDK的一部分,但Android官方文檔推薦使用該算法來(lái)實(shí)現(xiàn)硬盤(pán)緩存困檩。

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

最近使用頻率最少算法槽棍;注意LFU和LRU算法的不同之處逼友,LRU的淘汰規(guī)則是基于訪(fǎng)問(wèn)時(shí)間榴嗅,而LFU是基于訪(fǎng)問(wèn)次數(shù)的。思想:當(dāng)緩存滿(mǎn)的時(shí)候赖瞒,應(yīng)當(dāng)把訪(fǎng)問(wèn)頻次最少的數(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è)訪(fǎng)問(wèn)頻次吧兔,當(dāng)數(shù)據(jù)項(xiàng)被命中時(shí),訪(fǎng)問(wèn)頻次自增袍嬉,在淘汰的時(shí)候淘汰訪(fǎng)問(wèn)頻次最少的數(shù)據(jù)境蔼。在插入數(shù)據(jù)(插到數(shù)組的尾端)和訪(fǎng)問(wèn)數(shù)據(jù)(數(shù)組隨機(jī)訪(fǎng)問(wèn))的時(shí)候都能達(dá)到O(1)的時(shí)間復(fù)雜度;淘汰數(shù)據(jù)的時(shí)候伺通,通過(guò)選擇算法得到應(yīng)該淘汰的數(shù)據(jù)項(xiàng)在數(shù)組中的索引箍土,并將該索引位置的內(nèi)容替換為新來(lái)的數(shù)據(jù)內(nèi)容即可,這樣的話(huà)罐监,淘汰數(shù)據(jù)的操作時(shí)間復(fù)雜度為O(n)吴藻。

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

3.4 LRU2(Least Recently Used 2):

Least Recently Used 2,又叫最近最少使用twice弓柱。LRU2把被兩次訪(fǎng)問(wèn)過(guò)的對(duì)象放入緩存池沟堡,當(dāng)緩存池滿(mǎn)了之后,把有兩次最少使用的緩存對(duì)象淘汰矢空。因?yàn)樾枰檶?duì)象2次航罗,訪(fǎng)問(wèn)負(fù)載就會(huì)隨著緩存池的增加而增加。如果把LRU2用在大容量的緩存池中屁药,就會(huì)有問(wèn)題粥血。另外,LRU2還需要跟蹤不在緩存的對(duì)象酿箭,因?yàn)樗麄冞€沒(méi)有被第二次讀取复亏。LRU2比LRU好,且是adoptive to access模式缭嫡。

3.5 2Q(Two Queues):

2Q把被訪(fǎng)問(wèn)的數(shù)據(jù)放到LRU的緩存中缔御,如果這個(gè)對(duì)象再一次被訪(fǎng)問(wèn),2Q就把他轉(zhuǎn)移到第二個(gè)械巡、更大的LRU緩存刹淌。2Q淘汰緩存對(duì)象是為了保持第一個(gè)緩存池是第二個(gè)緩存池的1/3。當(dāng)緩存的訪(fǎng)問(wèn)負(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)包含的條目是最近只被使用過(guò)一次的茁裙,而第二個(gè)LRU(L2)包含的是最近被使用過(guò)兩次的條目塘砸。(因此,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è)緩存類(lèi)北救,所以在Android中可以直接使用LruCache實(shí)現(xiàn)內(nèi)存緩存。而DisLruCache目前在Android還不是Android SDK的一部分大猛,但Android官方文檔推薦使用該算法來(lái)實(shí)現(xiàn)硬盤(pán)緩存扭倾。

LruCache的使用很簡(jiǎn)單:

或者:

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

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

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

通過(guò)下面構(gòu)造函數(shù)來(lái)指定LinkedHashMap中雙向鏈表的結(jié)構(gòu)是訪(fǎng)問(wèn)順序還是插入順序模聋。

其中accessOrder設(shè)置為true則為訪(fǎng)問(wèn)順序,為false唠亚,則為插入順序链方。

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

輸出結(jié)果:

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

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

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

從LruCache的構(gòu)造函數(shù)中可以看到正是用了LinkedHashMap的訪(fǎng)問(wèn)順序罢维。

put()方法

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

trimToSize()方法

trimToSize()方法不斷地刪除LinkedHashMap中隊(duì)尾的元素平窘,即近期最少訪(fǎng)問(wèn)的吓肋,直到緩存大小小于最大值。

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

get()方法:

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

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

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

5萄窜、云閱讀書(shū)籍解析緩存(內(nèi)存緩存實(shí)例铃剔,可跳過(guò)本節(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)前頁(yè)面為普通頁(yè)或者標(biāo)題頁(yè)則加入緩存夷磕。

緩存函數(shù):

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

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

6于未、DiskLruCache的使用

6.1創(chuàng)建

DISK_CACHE_SIZE緩存大小。

6.2添加

DishLruCache緩存添加的操作通過(guò)Eidtor完成陡鹃,Editor為一個(gè)緩存對(duì)象的編輯對(duì)象烘浦。首先需要獲取圖片的url所對(duì)應(yīng)的key,根據(jù)key利用edit()來(lái)獲取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è)新的出來(lái)萄传。通過(guò)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è)圖片后蹭睡,通過(guò)這個(gè)OutputStream outputStream寫(xiě)入文件系統(tǒng)衍菱。

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

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

6.3緩存查找

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市锭碳,隨后出現(xiàn)的幾起案子袁稽,更是在濱河造成了極大的恐慌,老刑警劉巖擒抛,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件推汽,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡歧沪,警方通過(guò)查閱死者的電腦和手機(jī)歹撒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)诊胞,“玉大人栈妆,你說(shuō)我怎么就攤上這事∠峋” “怎么了鳞尔?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)早直。 經(jīng)常有香客問(wèn)我寥假,道長(zhǎng),這世上最難降的妖魔是什么霞扬? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任糕韧,我火速辦了婚禮,結(jié)果婚禮上喻圃,老公的妹妹穿的比我還像新娘萤彩。我一直安慰自己,他們只是感情好斧拍,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布雀扶。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪愚墓。 梳的紋絲不亂的頭發(fā)上予权,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音浪册,去河邊找鬼扫腺。 笑死,一個(gè)胖子當(dāng)著我的面吹牛村象,可吹牛的內(nèi)容都是我干的笆环。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼厚者,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼躁劣!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起籍救,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎渠抹,沒(méi)想到半個(gè)月后蝙昙,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡梧却,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年奇颠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片放航。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡烈拒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出广鳍,到底是詐尸還是另有隱情荆几,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布赊时,位于F島的核電站吨铸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏祖秒。R本人自食惡果不足惜诞吱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望竭缝。 院中可真熱鬧房维,春花似錦、人聲如沸抬纸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)湿故。三九已至暴浦,卻和暖如春溅话,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背歌焦。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工飞几, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人独撇。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓屑墨,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親纷铣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子卵史,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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