我們?cè)谔幚頂?shù)據(jù)的時(shí)候惑艇,第一步就會(huì)接觸到緩存。這里就來(lái)看看應(yīng)該選擇什么樣的緩存來(lái)解決你的問(wèn)題胸梆。
在一般的場(chǎng)景下敦捧,我們并不會(huì)去考慮緩存的所帶來(lái)的優(yōu)化與損耗,因?yàn)榇蟛糠謭?chǎng)景這一點(diǎn)損耗都可以忽略不計(jì)碰镜,但是在某些極限情況下,這就不能被忽略了习瑰。那么我們就需要來(lái)思考采用什么樣的方式去緩存數(shù)據(jù)绪颖。
首先,緩存可以被分為兩類:
- 內(nèi)存緩存
- 磁盤緩存甜奄,也可以稱為持久化
那么我們按照這兩者來(lái)分析下柠横,現(xiàn)在比較流行的幾種方式。
Memory Cache
NSCache
官方提供的緩存一般來(lái)說(shuō)已經(jīng)足夠使用了课兄,線程安全牍氛,功能也特別完善,擁有靈活的控制烟阐,在內(nèi)存不足時(shí)也會(huì)回收內(nèi)存搬俊。底層應(yīng)該是基于hash表的,所以性能表現(xiàn)也十分優(yōu)秀蜒茄。
但是系統(tǒng)實(shí)現(xiàn)的緩存失效策略卻是未知的唉擂,無(wú)法保證是LRU策略。同時(shí)NSCache會(huì)在系統(tǒng)回到后臺(tái)的時(shí)候清空緩存檀葛,如果你希望在app的生命周期內(nèi)都可以緩存玩祟,那么NSCache難以做到。
一般來(lái)說(shuō)NSCache是首選屿聋,除非需要一些特殊的要求空扎。
NSArray & NSDictionary
這兩者是系統(tǒng)內(nèi)置集合類型,可以用作緩存數(shù)據(jù)润讥,但是是非線程安全的转锈,在使用中需要特別小心,同時(shí)需要自己去控制緩存失效象对。
在數(shù)據(jù)量比較小的時(shí)候黑忱,這兩者都是沒(méi)有問(wèn)題的,但是在數(shù)據(jù)量變得龐大的時(shí)候就會(huì)有一定的性能問(wèn)題。
由于NSArray是數(shù)組實(shí)現(xiàn)甫煞,位置查找的效率為1菇曲,內(nèi)容查找的效率為n,所以在大量頻繁的內(nèi)容查找中抚吠,會(huì)降低其性能常潮。這時(shí)候推薦NSSet或者NSOrderedSet來(lái)替代NSArray。NSSet的底層是hash表楷力。
在使用hash表的時(shí)候喊式,如何來(lái)更好的實(shí)現(xiàn)其查找性能呢,就需要保持key的hash隨機(jī)分布萧朝。一般來(lái)說(shuō)我們都會(huì)使用string作為key岔留,在自己實(shí)現(xiàn)的key值別忘了重寫hash。
在某些情境下检柬,使用集合類型也非常有效献联。
其他
也可以根據(jù)自己的需求來(lái)設(shè)計(jì)自己的容器類,比如自平衡二叉樹(shù)何址、B樹(shù)等里逆。不過(guò)首先要了解上述幾種是否已經(jīng)滿足自己的需求。
Persistent
持久化緩存擁有多種不同的數(shù)據(jù)格式和存儲(chǔ)方式用爪,這里按照幾種方式和開(kāi)源庫(kù)來(lái)看看各自的方案原押。
NSUserDefaults
這是系統(tǒng)提供的最簡(jiǎn)單的一種保存數(shù)據(jù)的方式,自帶了緩存和同步機(jī)制偎血,利用的是NSCoding的方式诸衔,所以NSCoding擁有的缺陷UserDefault也會(huì)擁有。
當(dāng)數(shù)據(jù)量增多變大烁巫,會(huì)導(dǎo)致plist文件太大署隘,從而影響加載性能,所以只能保存少量的小型數(shù)據(jù)亚隙。
NSCoding
這是系統(tǒng)提供的持久化方案磁餐,不僅僅保存了數(shù)據(jù),同時(shí)也保存了類別信息阿弃。但這也帶來(lái)了部分缺陷诊霹,那就是數(shù)據(jù)兼容問(wèn)題。
當(dāng)軟件升級(jí)時(shí)渣淳,修改了類名脾还,或者改動(dòng)了內(nèi)部成員實(shí)現(xiàn),就可能導(dǎo)致數(shù)據(jù)錯(cuò)誤設(shè)置崩潰入愧。所以需要小心控制數(shù)據(jù)版本信息鄙漏。
由于這種方式是一次性的讀取與寫入嗤谚,在數(shù)據(jù)量大的時(shí)候也會(huì)產(chǎn)生一些問(wèn)題。同時(shí)這種方式并不適合部分讀取部分修改的場(chǎng)景怔蚌,如果數(shù)據(jù)比較大需要重新考慮巩步。
JSON
另一種代替NSCoding的方式便是使用JSON來(lái)保存,雖然在數(shù)據(jù)兼容性上會(huì)比NSCoding稍微優(yōu)秀一些桦踊,但依然沒(méi)有根本解決這個(gè)問(wèn)題椅野,所以這是一個(gè)可選方案。
YYKit
YYKit使用了LRU策略籍胯,明確了緩存失效策略竟闪。
內(nèi)存緩存使用了線性鏈表+NSDictionary來(lái)實(shí)現(xiàn),由于LRU的特性杖狼,插入永遠(yuǎn)在開(kāi)始炼蛤,而刪除永遠(yuǎn)在結(jié)尾,所以擁有較高的性能蝶涩。但是查找還是依賴于hash表來(lái)實(shí)現(xiàn)鲸湃。這樣在插入和查找都避免了對(duì)方的缺陷,實(shí)現(xiàn)了更加高效的結(jié)果子寓。缺點(diǎn)是需要同時(shí)保存和修改兩份數(shù)據(jù)索引。
磁盤緩存使用了sqlite來(lái)保存文件緩存信息(filename, last_modify_time)笋除,所以在讀寫小數(shù)據(jù)的時(shí)候(20KB)會(huì)直接在sqlite中讀寫斜友,而不會(huì)生成一個(gè)獨(dú)立的文件。所以在小文件和未命中的情況下效率會(huì)高很多垃它。而讀寫大文件時(shí)鲜屏,效率會(huì)降低一些,考慮到sqlite的緩存和執(zhí)行国拇,并不會(huì)降低太多洛史。由于sqlite對(duì)時(shí)間創(chuàng)建了索引,所以在緩存過(guò)期查找上面會(huì)優(yōu)秀一些酱吝。這種設(shè)計(jì)解決了小文件和未命中的效率問(wèn)題也殖,但是并不能實(shí)現(xiàn)高并發(fā)讀寫文件。
這種按照數(shù)據(jù)量來(lái)區(qū)分?jǐn)?shù)據(jù)存儲(chǔ)方式的方法解決了大文件和小文件之間的性能差別务热,但也給緩存系統(tǒng)帶來(lái)了一定的復(fù)雜性忆嗜。同時(shí)如果sqlite的索引失效會(huì)導(dǎo)致查找效率的降低。
YYCache帶來(lái)了一種通用型的存儲(chǔ)方式崎岂,但在很多時(shí)候還是需要自己來(lái)實(shí)現(xiàn)特定的需求捆毫。
PINCache
使用了大量的Lock來(lái)處理多線程讀寫,擁有異步讀寫接口冲甘,沒(méi)有太多的特別優(yōu)化绩卤。
磁盤緩存單純使用了文件緩存途样,在初始化的時(shí)候就把整個(gè)目錄及其元素的屬性讀到內(nèi)存,來(lái)提高效率濒憋,但是使用的是數(shù)組存儲(chǔ)何暇,效率一般。
SPTPersistentCache
他將數(shù)據(jù)信息通過(guò)memory map的方式寫到了文件頭部跋炕,說(shuō)是為了并發(fā)讀寫赖晶,但這也時(shí)每次更新updateTime需要寫整個(gè)文件,這樣必定會(huì)導(dǎo)致性能降低辐烂。個(gè)人建議還是把文件信息寫到另一個(gè)文件中遏插,方便內(nèi)存緩存。
這種方式比較適合的場(chǎng)景是只讀數(shù)據(jù)纠修,對(duì)于經(jīng)常變化的數(shù)據(jù)反而可能會(huì)降低性能胳嘲。
Haneke & SDWebImage
這兩者非常相似,Haneke功能更少扣草,但是更加緊湊了牛,代碼結(jié)構(gòu)也更加好。而SDWebImage功能非常完善辰妙,使用的人也非常的多鹰祸。但也并非沒(méi)有瑕疵。
圖片緩存讀取全部在一個(gè)子線程中進(jìn)行密浑,導(dǎo)致在高并發(fā)讀取的時(shí)候會(huì)阻塞線程蛙婴,同樣下載和解碼也會(huì)有類似的問(wèn)題。這么設(shè)計(jì)同時(shí)也是為了保證線程安全尔破,所以采用了順序隊(duì)列的操作街图,但是對(duì)于單文件來(lái)說(shuō),這樣是正確的懒构,對(duì)于多文件來(lái)說(shuō)沒(méi)有必要這樣做餐济。在目前移動(dòng)端以及pc端來(lái)看,性能的瓶頸還不在這個(gè)地方胆剧,依然在IO上面絮姆,所以除非特殊情況,不會(huì)出現(xiàn)性能問(wèn)題赞赖。
圖片的二次處理能力不夠(比如手動(dòng)加圓角滚朵,裁剪,濾鏡)前域,需要自己去處理并且緩存辕近,這對(duì)于一個(gè)圖片庫(kù)來(lái)說(shuō)是一個(gè)遺憾,好在目前大部分工作CDN都會(huì)幫我們做掉匿垄。
預(yù)加載圖片無(wú)法和正常加載使用同一套機(jī)制移宅,預(yù)加載和正常加載如果同時(shí)觸發(fā)會(huì)加載2次归粉。SD沒(méi)有考慮到預(yù)加載和正常加載使用同一個(gè)Operation緩存,導(dǎo)致雙方都會(huì)觸發(fā)真實(shí)的下載漏峰,從而浪費(fèi)了流量糠悼。
作為一個(gè)圖片庫(kù),圖片一般內(nèi)容都比較大浅乔,所以采用了文件緩存的機(jī)制倔喂,使用key作為文件名。由于文件系統(tǒng)自身?yè)碛械木彺婢肝栽诓檎业男噬喜⒉坏汀?/p>
FastImageCache
這也是一個(gè)圖片緩存方案席噩,增加了處理圖片的一些中間件。
該作者認(rèn)為效率問(wèn)題主要出現(xiàn)在圖片從磁盤讀取到內(nèi)存贤壁,再進(jìn)行解壓悼枢,以及渲染前的內(nèi)存拷貝。解決這類問(wèn)題的最好方法就是進(jìn)行memory map脾拆,將處理好的內(nèi)容直接寫入文件馒索,這樣在下一次載入的時(shí)候就不需要重新處理了。
作者也指出了這種方式會(huì)導(dǎo)致一張高壓縮率的圖片名船,進(jìn)行內(nèi)存映射后會(huì)變得很大绰上,這一非常大的缺陷。
內(nèi)存映射也是一種很好的方案渠驼,在存儲(chǔ)資源豐富渔期,而處理需要很長(zhǎng)時(shí)間的情況下,是最簡(jiǎn)單的處理方式渴邦。隨著現(xiàn)在設(shè)備性能的提高,一般不會(huì)存在處理的性能瓶頸拘哨,所以也需要按情景來(lái)判斷谋梭。
最后
這里分析了幾種內(nèi)存緩存和磁盤緩存的情況牡直,一般內(nèi)存緩存較為簡(jiǎn)單叙身,不會(huì)有太多的性能問(wèn)題,而磁盤緩存擁有很多的方案叙量,每種方案都有各自的適用場(chǎng)景产镐,需要根據(jù)自身的實(shí)際情況來(lái)選擇隘庄。
這里所列的幾種磁盤緩存都比較簡(jiǎn)單,之后會(huì)介紹一些比較復(fù)雜的存儲(chǔ)方案癣亚。