自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

緩存、緩存算法和緩存框架簡(jiǎn)介

開(kāi)發(fā) 開(kāi)發(fā)工具 算法
很久很久以前,在還沒(méi)有緩存的時(shí)候……用戶經(jīng)常是去請(qǐng)求一個(gè)對(duì)象,而這個(gè)對(duì)象是從數(shù)據(jù)庫(kù)去取,然后,這個(gè)對(duì)象變得越來(lái)越大,這個(gè)用戶每次的請(qǐng)求時(shí)間也越來(lái)越長(zhǎng)了,這也把數(shù)據(jù)庫(kù)弄得很痛苦

引言

我們都聽(tīng)過(guò) cache,當(dāng)你問(wèn)他們是什么是緩存的時(shí)候,他們會(huì)給你一個(gè)完美的答案,可是他們不知道緩存是怎么構(gòu)建的,或者沒(méi)有告訴你應(yīng)該采用什么標(biāo)準(zhǔn)去選擇緩存框架。在這邊文章,我們會(huì)去討論緩存,緩存算法,緩存框架以及哪個(gè)緩存框架會(huì)更好。

面試

“緩存就是存貯數(shù)據(jù)(使用頻繁的數(shù)據(jù))的臨時(shí)地方,因?yàn)槿≡紨?shù)據(jù)的代價(jià)太大了,所以我可以取得快一些。”

這就是 programmer one (programmer one 是一個(gè)面試者)在面試中的回答(一個(gè)月前,他向公司提交了簡(jiǎn)歷,想要應(yīng)聘要求在緩存,緩存框架,大規(guī)模數(shù)據(jù)操作有著豐富經(jīng)驗(yàn)的 java 開(kāi)發(fā)職位)。

programmer one 通過(guò) hash table 實(shí)現(xiàn)了他自己的緩存,但是他知道的只是他的緩存和他那存儲(chǔ)著150條記錄的 hash table,這就是他認(rèn)為的大規(guī)模數(shù)據(jù)(緩存 = hashtable,只需要在 hash table 查找就好了),所以,讓我們來(lái)看看面試的過(guò)程吧。

面試官:你選擇的緩存方案,是基于什么標(biāo)準(zhǔn)的?

programmer one:呃,(想了5分鐘)嗯,基于,基于,基于數(shù)據(jù)(咳嗽……)

面試官:excese me ! 能不能重復(fù)一下?

programmer one:數(shù)據(jù)?!

面試官:好的。說(shuō)說(shuō)幾種緩存算法以及它們的作用

programmer one:(凝視著面試官,臉上露出了很奇怪的表情,沒(méi)有人知道原來(lái)人類可以做出這種表情  )

面試官:好吧,那我換個(gè)說(shuō)法,當(dāng)緩存達(dá)到容量時(shí),會(huì)怎么做?

programmer one:容量?嗯(思考……hash table 的容量時(shí)沒(méi)有限制的,我能任意增加條目,它會(huì)自動(dòng)擴(kuò)充容量的)(這是 programmer one 的想法,但是他沒(méi)有說(shuō)出來(lái))

面試官對(duì) programmer one 表示感謝(面試過(guò)程持續(xù)了10分鐘),之后一個(gè)女士走過(guò)來(lái)說(shuō):謝謝你的時(shí)間,我們會(huì)給你打電話的,祝你好心情。這是 programmer one 最糟糕的面試(他沒(méi)有看到招聘對(duì)求職者有豐富的緩存經(jīng)驗(yàn)背景要求,實(shí)際上,他只看到了豐厚的報(bào)酬  )。

說(shuō)到做到

programmer one 離開(kāi)之后,他想要知道這個(gè)面試者說(shuō)的問(wèn)題和答案,所以他上網(wǎng)去查,programmer one 對(duì)緩存一無(wú)所知,除了:當(dāng)我需要緩存的時(shí)候,我就會(huì)用 hash table。

在他使用了他最愛(ài)的搜索引擎搜索之后,他找到了一篇很不錯(cuò)的關(guān)于緩存文章,并且開(kāi)始去閱讀……

#p#

為什么我們需要緩存?

很久很久以前,在還沒(méi)有緩存的時(shí)候……用戶經(jīng)常是去請(qǐng)求一個(gè)對(duì)象,而這個(gè)對(duì)象是從數(shù)據(jù)庫(kù)去取,然后,這個(gè)對(duì)象變得越來(lái)越大,這個(gè)用戶每次的請(qǐng)求時(shí)間也越來(lái)越長(zhǎng)了,這也把數(shù)據(jù)庫(kù)弄得很痛苦,他無(wú)時(shí)不刻不在工作。所以,這個(gè)事情就把用戶和數(shù)據(jù)庫(kù)弄得很生氣,接著就有可能發(fā)生下面兩件事情:

1.用戶很煩,在抱怨,甚至不去用這個(gè)應(yīng)用了(這是大多數(shù)情況下都會(huì)發(fā)生的)

2.數(shù)據(jù)庫(kù)為打包回家,離開(kāi)這個(gè)應(yīng)用,然后,就出現(xiàn)了大麻煩(沒(méi)地方去存儲(chǔ)數(shù)據(jù)了)(發(fā)生在極少數(shù)情況下)

上帝派來(lái)了緩存

在幾年之后,IBM(60年代)的研究人員引進(jìn)了一個(gè)新概念,它叫“緩存”。

什么是緩存?

正如開(kāi)篇所講,緩存是“存貯數(shù)據(jù)(使用頻繁的數(shù)據(jù))的臨時(shí)地方,因?yàn)槿≡紨?shù)據(jù)的代價(jià)太大了,所以我可以取得快一些。”

緩存可以認(rèn)為是數(shù)據(jù)的池,這些數(shù)據(jù)是從數(shù)據(jù)庫(kù)里的真實(shí)數(shù)據(jù)復(fù)制出來(lái)的,并且為了能別取回,被標(biāo)上了標(biāo)簽(鍵 ID)。太棒了

programmer one 已經(jīng)知道這點(diǎn)了,但是他還不知道下面的緩存術(shù)語(yǔ)。

緩存、緩存算法和緩存框架簡(jiǎn)介

命中:

當(dāng)客戶發(fā)起一個(gè)請(qǐng)求(我們說(shuō)他想要查看一個(gè)產(chǎn)品信息),我們的應(yīng)用接受這個(gè)請(qǐng)求,并且如果是在第一次檢查緩存的時(shí)候,需要去數(shù)據(jù)庫(kù)讀取產(chǎn)品信息。

如果在緩存中,一個(gè)條目通過(guò)一個(gè)標(biāo)記被找到了,這個(gè)條目就會(huì)被使用、我們就叫它緩存命中。所以,命中率也就不難理解了。

Cache Miss:

但是這里需要注意兩點(diǎn):

1. 如果還有緩存的空間,那么,沒(méi)有命中的對(duì)象會(huì)被存儲(chǔ)到緩存中來(lái)。

2. 如果緩存慢了,而又沒(méi)有命中緩存,那么就會(huì)按照某一種策略,把緩存中的舊對(duì)象踢出,而把新的對(duì)象加入緩存池。而這些策略統(tǒng)稱為替代策略(緩存算法),這些策略會(huì)決定到底應(yīng)該提出哪些對(duì)象。

存儲(chǔ)成本:

當(dāng)沒(méi)有命中時(shí),我們會(huì)從數(shù)據(jù)庫(kù)取出數(shù)據(jù),然后放入緩存。而把這個(gè)數(shù)據(jù)放入緩存所需要的時(shí)間和空間,就是存儲(chǔ)成本。

索引成本:

和存儲(chǔ)成本相仿。

失效:

當(dāng)存在緩存中的數(shù)據(jù)需要更新時(shí),就意味著緩存中的這個(gè)數(shù)據(jù)失效了。

替代策略:

當(dāng)緩存沒(méi)有命中時(shí),并且緩存容量已經(jīng)滿了,就需要在緩存中踢出一個(gè)老的條目,加入一條新的條目,而到底應(yīng)該踢出什么條目,就由替代策略決定。

最優(yōu)替代策略:

最優(yōu)的替代策略就是想把緩存中最沒(méi)用的條目給踢出去,但是未來(lái)是不能夠被預(yù)知的,所以這種策略是不可能實(shí)現(xiàn)的。但是有很多策略,都是朝著這個(gè)目前去努力。

Java 街惡夢(mèng):

當(dāng) programmer one 在讀這篇文章的時(shí)候,他睡著了,并且做了個(gè)惡夢(mèng)(每個(gè)人都有做惡夢(mèng)的時(shí)候)。

programmer one:nihahha,我要把你弄失效?。ǒ偪竦臓顟B(tài))

緩存對(duì)象:別別,讓我活著,他們還需要我,我還有孩子。

programmer one:每個(gè)緩存對(duì)象在失效之前都會(huì)那樣說(shuō)。你從什么時(shí)候開(kāi)始有孩子的?不用擔(dān)心,現(xiàn)在就永遠(yuǎn)消失吧!

哈哈哈哈哈……programmer one 恐怖的笑著,但是警笛打破了沉靜,警察把 programmer one 抓了起來(lái),并且控告他殺死了(失效)一個(gè)仍需被使用的緩存對(duì)象,他被押到了監(jiān)獄。

programmer one 突然醒了,他被嚇到了,渾身是汗,他開(kāi)始環(huán)顧四周,發(fā)現(xiàn)這確實(shí)是個(gè)夢(mèng),然后趕緊繼續(xù)閱讀這篇文章,努力的消除自己的恐慌。

在programmer one 醒來(lái)之后,他又開(kāi)始閱讀文章了。

#p#

緩存算法

沒(méi)有人能說(shuō)清哪種緩存算法優(yōu)于其他的緩存算法

Least Frequently Used(LFU):

大家好,我是 LFU,我會(huì)計(jì)算為每個(gè)緩存對(duì)象計(jì)算他們被使用的頻率。我會(huì)把最不常用的緩存對(duì)象踢走。

Least Recently User(LRU):

我是 LRU 緩存算法,我把最近最少使用的緩存對(duì)象給踢走。

我總是需要去了解在什么時(shí)候,用了哪個(gè)緩存對(duì)象。如果有人想要了解我為什么總能把最近最少使用的對(duì)象踢掉,是非常困難的。

瀏覽器就是使用了我(LRU)作為緩存算法。新的對(duì)象會(huì)被放在緩存的頂部,當(dāng)緩存達(dá)到了容量極限,我會(huì)把底部的對(duì)象踢走,而技巧就是:我會(huì)把最新被訪問(wèn)的緩存對(duì)象,放到緩存池的頂部。

所以,經(jīng)常被讀取的緩存對(duì)象就會(huì)一直呆在緩存池中。有兩種方法可以實(shí)現(xiàn)我,array 或者是 linked list。

我的速度很快,我也可以被數(shù)據(jù)訪問(wèn)模式適配。我有一個(gè)大家庭,他們都可以完善我,甚至做的比我更好(我確實(shí)有時(shí)會(huì)嫉妒,但是沒(méi)關(guān)系)。我家庭的一些成員包括 LRU2 和 2Q,他們就是為了完善 LRU 而存在的。

Least Recently Used 2(LRU2):

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

Two Queues(2Q):

我是 Two Queues;我把被訪問(wèn)的數(shù)據(jù)放到 LRU 的緩存中,如果這個(gè)對(duì)象再一次被訪問(wèn),我就把他轉(zhuǎn)移到第二個(gè)、更大的 LRU 緩存。

我踢走緩存對(duì)象是為了保持第一個(gè)緩存池是第二個(gè)緩存池的1/3。當(dāng)緩存的訪問(wèn)負(fù)載是固定的時(shí)候,把 LRU 換成 LRU2,就比增加緩存的容量更好。這種機(jī)制使得我比 LRU2 更好,我也是 LRU 家族中的一員,而且是 adoptive to access 模式 。

Adaptive Replacement Cache(ARC):

我是 ARC,有人說(shuō)我是介于 LRU 和 LFU 之間,為了提高效果,我是由2個(gè) LRU 組成,第一個(gè),也就是 L1,包含的條目是最近只被使用過(guò)一次的,而第二個(gè) LRU,也就是 L2,包含的是最近被使用過(guò)兩次的條目。因此, L1 放的是新的對(duì)象,而 L2 放的是常用的對(duì)象。所以,別人才會(huì)認(rèn)為我是介于 LRU 和 LFU 之間的,不過(guò)沒(méi)關(guān)系,我不介意。

我被認(rèn)為是性能最好的緩存算法之一,能夠自調(diào),并且是低負(fù)載的。我也保存著歷史對(duì)象,這樣,我就可以記住那些被移除的對(duì)象,同時(shí),也讓我可以看到被移除的對(duì)象是否可以留下,取而代之的是踢走別的對(duì)象。我的記憶力很差,但是我很快,適用性也強(qiáng)。

Most Recently Used(MRU):

我是 MRU,和 LRU 是對(duì)應(yīng)的。我會(huì)移除最近最多被使用的對(duì)象,你一定會(huì)問(wèn)我為什么。好吧,讓我告訴你,當(dāng)一次訪問(wèn)過(guò)來(lái)的時(shí)候,有些事情是無(wú)法預(yù)測(cè)的,并且在緩存系統(tǒng)中找出最少最近使用的對(duì)象是一項(xiàng)時(shí)間復(fù)雜度非常高的運(yùn)算,這就是為什么我是最好的選擇。

我是數(shù)據(jù)庫(kù)內(nèi)存緩存中是多么的常見(jiàn)!每當(dāng)一次緩存記錄的使用,我會(huì)把它放到棧的頂端。當(dāng)棧滿了的時(shí)候,你猜怎么著?我會(huì)把棧頂?shù)膶?duì)象給換成新進(jìn)來(lái)的對(duì)象!

First in First out(FIFO):

我是先進(jìn)先出,我是一個(gè)低負(fù)載的算法,并且對(duì)緩存對(duì)象的管理要求不高。我通過(guò)一個(gè)隊(duì)列去跟蹤所有的緩存對(duì)象,最近最常用的緩存對(duì)象放在后面,而更早的緩存對(duì)象放在前面,當(dāng)緩存容量滿時(shí),排在前面的緩存對(duì)象會(huì)被踢走,然后把新的緩存對(duì)象加進(jìn)去。我很快,但是我并不適用。

Second Chance:

大家好,我是 second chance,我是通過(guò) FIFO 修改而來(lái)的,被大家叫做 second chance 緩存算法,我比 FIFO 好的地方是我改善了 FIFO 的成本。我是 FIFO 一樣也是在觀察隊(duì)列的前端,但是很FIFO的立刻踢出不同,我會(huì)檢查即將要被踢出的對(duì)象有沒(méi)有之前被使用過(guò)的標(biāo)志(1一個(gè) bit 表示),沒(méi)有沒(méi)有被使用過(guò),我就把他踢出;否則,我會(huì)把這個(gè)標(biāo)志位清除,然后把這個(gè)緩存對(duì)象當(dāng)做新增緩存對(duì)象加入隊(duì)列。你可以想象就這就像一個(gè)環(huán)隊(duì)列。當(dāng)我再一次在隊(duì)頭碰到這個(gè)對(duì)象時(shí),由于他已經(jīng)沒(méi)有這個(gè)標(biāo)志位了,所以我立刻就把他踢開(kāi)了。我在速度上比 FIFO 快。

CLock:

我是 Clock,一個(gè)更好的 FIFO,也比 second chance 更好。因?yàn)槲也粫?huì)像 second chance 那樣把有標(biāo)志的緩存對(duì)象放到隊(duì)列的尾部,但是也可以達(dá)到 second chance 的效果。

我持有一個(gè)裝有緩存對(duì)象的環(huán)形列表,頭指針指向列表中最老的緩存對(duì)象。當(dāng)緩存 miss 發(fā)生并且沒(méi)有新的緩存空間時(shí),我會(huì)問(wèn)問(wèn)指針指向的緩存對(duì)象的標(biāo)志位去決定我應(yīng)該怎么做。如果標(biāo)志是0,我會(huì)直接用新的緩存對(duì)象替代這個(gè)緩存對(duì)象;如果標(biāo)志位是1,我會(huì)把頭指針遞增,然后重復(fù)這個(gè)過(guò)程,知道新的緩存對(duì)象能夠被放入。我比 second chance 更快。

Simple time-based:

我是 simple time-based 緩存算法,我通過(guò)絕對(duì)的時(shí)間周期去失效那些緩存對(duì)象。對(duì)于新增的對(duì)象,我會(huì)保存特定的時(shí)間。我很快,但是我并不適用。

Extended time-based expiration:

我是 extended time-based expiration 緩存算法,我是通過(guò)相對(duì)時(shí)間去失效緩存對(duì)象的;對(duì)于新增的緩存對(duì)象,我會(huì)保存特定的時(shí)間,比如是每5分鐘,每天的12點(diǎn)。

Sliding time-based expiration:

我是 sliding time-based expiration,與前面不同的是,被我管理的緩存對(duì)象的生命起點(diǎn)是在這個(gè)緩存的最后被訪問(wèn)時(shí)間算起的。我很快,但是我也不太適用。

其他的緩存算法還考慮到了下面幾點(diǎn):

成本:如果緩存對(duì)象有不同的成本,應(yīng)該把那些難以獲得的對(duì)象保存下來(lái)。

容量:如果緩存對(duì)象有不同的大小,應(yīng)該把那些大的緩存對(duì)象清除,這樣就可以讓更多的小緩存對(duì)象進(jìn)來(lái)了。

時(shí)間:一些緩存還保存著緩存的過(guò)期時(shí)間。電腦會(huì)失效他們,因?yàn)樗麄円呀?jīng)過(guò)期了。

根據(jù)緩存對(duì)象的大小而不管其他的緩存算法可能是有必要的。

#p#

電子郵件!

在讀完這篇文章之后,programmer one 想了一會(huì)兒,然后決定給作者發(fā)封郵件,他感覺(jué)作者的名字在哪聽(tīng)過(guò),但是已經(jīng)想不起來(lái)了。不管怎樣,他還是把郵件發(fā)送出來(lái)了,他詢問(wèn)了作者在分布式環(huán)境中,緩存是怎么樣工作的。

文章的作者收到了郵件,具有諷刺意味的是,這個(gè)作者就是面試 programmer one 的人  ,作者回復(fù)了……

在這一部分中,我們來(lái)看看如何實(shí)現(xiàn)這些著名的緩存算法。以下的代碼只是示例用的,如果你想自己實(shí)現(xiàn)緩存算法,可能自己還得加上一些額外的工作。

LeftOver 機(jī)制

在 programmer one 閱讀了文章之后,他接著看了文章的評(píng)論,其中有一篇評(píng)論提到了 leftover 機(jī)制——random cache。

Random Cache

我是隨機(jī)緩存,我隨意的替換緩存實(shí)體,沒(méi)人敢抱怨。你可以說(shuō)那個(gè)被替換的實(shí)體很倒霉。通過(guò)這些行為,我隨意的去處緩存實(shí)體。我比 FIFO 機(jī)制好,在某些情況下,我甚至比 LRU 好,但是,通常LRU都會(huì)比我好。

現(xiàn)在是評(píng)論時(shí)間

當(dāng) programmer one 繼續(xù)閱讀評(píng)論的時(shí)候,他發(fā)現(xiàn)有個(gè)評(píng)論非常有趣,這個(gè)評(píng)論實(shí)現(xiàn)了一些緩存算法,應(yīng)該說(shuō)這個(gè)評(píng)論做了一個(gè)鏈向評(píng)論者網(wǎng)站的鏈接,programmer one順著鏈接到了那個(gè)網(wǎng)站,接著閱讀。

看看緩存元素(緩存實(shí)體)

  1. public class CacheElement  
  2.  {  
  3.      private Object objectValue;  
  4.      private Object objectKey;  
  5.      private int index;  
  6.      private int hitCount; // getters and setters  
  7.  } 

這個(gè)緩存實(shí)體擁有緩存的key和value,這個(gè)實(shí)體的數(shù)據(jù)結(jié)構(gòu)會(huì)被以下所有緩存算法用到。

緩存算法的公用代碼

  1. public final synchronized void addElement(Object key, Object value)  
  2. {  
  3.     int index;  
  4.     Object obj;  
  5.     // get the entry from the table  
  6.     obj = table.get(key);  
  7.     // If we have the entry already in our table  
  8.     // then get it and replace only its value.  
  9.     obj = table.get(key);  
  10.    
  11.     if (obj != null)  
  12.     {  
  13.         CacheElement element;  
  14.         element = (CacheElement) obj;  
  15.         element.setObjectValue(value);  
  16.         element.setObjectKey(key);  
  17.         return;  
  18.     }  

上面的代碼會(huì)被所有的緩存算法實(shí)現(xiàn)用到。這段代碼是用來(lái)檢查緩存元素是否在緩存中了,如果是,我們就替換它,但是如果我們找不到這個(gè) key 對(duì)應(yīng)的緩存,我們會(huì)怎么做呢?那我們就來(lái)深入的看看會(huì)發(fā)生什么吧!

現(xiàn)場(chǎng)訪問(wèn)

今天的專題很特殊,因?yàn)槲覀冇刑厥獾目腿耍聦?shí)上他們是我們想要聽(tīng)的與會(huì)者,但是首先,先介紹一下我們的客人:Random Cache,F(xiàn)IFO Cache。讓我們從 Random Cache開(kāi)始。

看看隨機(jī)緩存的實(shí)現(xiàn)

  1. public final synchronized void addElement(Object key, Object value)  
  2. {  
  3.     int index;  
  4.     Object obj;  
  5.     obj = table.get(key);  
  6.     if (obj != null)  
  7.     {  
  8.         CacheElement element;// Just replace the value.  
  9.         element = (CacheElement) obj;  
  10.         element.setObjectValue(value);  
  11.         element.setObjectKey(key);  
  12.         return;  
  13.     }// If we haven't filled the cache yet, put it at the end.  
  14.     if (!isFull())  
  15.     {  
  16.         index = numEntries;  
  17.         ++numEntries;  
  18.     }  
  19.     else { // Otherwise, replace a random entry.  
  20.         index = (int) (cache.length * random.nextFloat());  
  21.         table.remove(cache[index].getObjectKey());  
  22.     }  
  23.     cache[index].setObjectValue(value);  
  24.     cache[index].setObjectKey(key);  
  25.     table.put(key, cache[index]);  

看看FIFO緩算法的實(shí)現(xiàn)

  1. public final synchronized void addElement(Objectkey, Object value)  
  2. {  
  3.     int index;  
  4.     Object obj;  
  5.     obj = table.get(key);  
  6.     if (obj != null)  
  7.     {  
  8.         CacheElement element; // Just replace the value.  
  9.         element = (CacheElement) obj;  
  10.         element.setObjectValue(value);  
  11.         element.setObjectKey(key);  
  12.         return;  
  13.     }  
  14.     // If we haven't filled the cache yet, put it at the end.  
  15.     if (!isFull())  
  16.     {  
  17.         index = numEntries;  
  18.         ++numEntries;  
  19.     }  
  20.     else { // Otherwise, replace the current pointer,  
  21.            // entry with the new one.  
  22.         index = current;  
  23.         // in order to make Circular FIFO  
  24.         if (++current >= cache.length)  
  25.             current = 0;  
  26.         table.remove(cache[index].getObjectKey());  
  27.     }  
  28.     cache[index].setObjectValue(value);  
  29.     cache[index].setObjectKey(key);  
  30.     table.put(key, cache[index]);  

看看LFU緩存算法的實(shí)現(xiàn)

  1. public synchronized Object getElement(Object key)  
  2. {  
  3.     Object obj;  
  4.     obj = table.get(key);  
  5.     if (obj != null)  
  6.     {  
  7.         CacheElement element = (CacheElement) obj;  
  8.         element.setHitCount(element.getHitCount() + 1);  
  9.         return element.getObjectValue();  
  10.     }  
  11.     return null;  
  12. }  
  13. public final synchronized void addElement(Object key, Object value)  
  14. {  
  15.     Object obj;  
  16.     obj = table.get(key);  
  17.     if (obj != null)  
  18.     {  
  19.         CacheElement element; // Just replace the value.  
  20.         element = (CacheElement) obj;  
  21.         element.setObjectValue(value);  
  22.         element.setObjectKey(key);  
  23.         return;  
  24.     }  
  25.     if (!isFull())  
  26.     {  
  27.         index = numEntries;  
  28.         ++numEntries;  
  29.     }  
  30.     else 
  31.     {  
  32.         CacheElement element = removeLfuElement();  
  33.         index = element.getIndex();  
  34.         table.remove(element.getObjectKey());  
  35.     }  
  36.     cache[index].setObjectValue(value);  
  37.     cache[index].setObjectKey(key);  
  38.     cache[index].setIndex(index);  
  39.     table.put(key, cache[index]);  
  40. }  
  41. public CacheElement removeLfuElement()  
  42. {  
  43.     CacheElement[] elements = getElementsFromTable();  
  44.     CacheElement leastElement = leastHit(elements);  
  45.     return leastElement;  
  46. }  
  47. public static CacheElement leastHit(CacheElement[] elements)  
  48. {  
  49.     CacheElement lowestElement = null;  
  50.     for (int i = 0; i < elements.length; i++)  
  51.     {  
  52.         CacheElement element = elements[i];  
  53.         if (lowestElement == null)  
  54.         {  
  55.             lowestElement = element;  
  56.         }  
  57.         else {  
  58.             if (element.getHitCount() < lowestElement.getHitCount())  
  59.             {  
  60.                 lowestElement = element;  
  61.             }  
  62.         }  
  63.     }  
  64.     return lowestElement;  

今天的專題很特殊,因?yàn)槲覀冇刑厥獾目腿?,事?shí)上他們是我們想要聽(tīng)的與會(huì)者,但是首先,先介紹一下我們的客人:Random Cache, FIFO Cache。讓我們從 Random Cache開(kāi)始。

最重點(diǎn)的代碼,就應(yīng)該是 leastHit 這個(gè)方法,這段代碼就是把hitCount 最低的元素找出來(lái),然后刪除,給新進(jìn)的緩存元素留位置。

看看LRU緩存算法實(shí)現(xiàn)

  1. private void moveToFront(int index)  
  2. {  
  3.     int nextIndex, prevIndex;  
  4.     if(head != index)  
  5.     {  
  6.         nextIndex = next[index];  
  7.         prevIndex = prev[index];  
  8.         // Only the head has a prev entry that is an invalid index  
  9.         // so we don't check.  
  10.         next[prevIndex] = nextIndex;  
  11.         // Make sure index is valid. If it isn't, we're at the tail  
  12.         // and don't set prev[next].  
  13.         if(nextIndex >= 0)  
  14.             prev[nextIndex] = prevIndex;  
  15.         else 
  16.             tail = prevIndex;  
  17.         prev[index] = -1;  
  18.         next[index] = head;  
  19.         prev[head] = index;  
  20.         head = index;  
  21.     }  
  22. }  
  23. public final synchronized void addElement(Object key, Object value)  
  24. {  
  25.     int index;Object obj;  
  26.     obj = table.get(key);  
  27.     if(obj != null)  
  28.     {  
  29.         CacheElement entry;  
  30.         // Just replace the value, but move it to the front.  
  31.         entry = (CacheElement)obj;  
  32.         entry.setObjectValue(value);  
  33.         entry.setObjectKey(key);  
  34.         moveToFront(entry.getIndex());  
  35.         return;  
  36.     }  
  37.     // If we haven't filled the cache yet, place in next available  
  38.     // spot and move to front.  
  39.     if(!isFull())  
  40.     {  
  41.         if(_numEntries > 0)  
  42.         {  
  43.             prev[_numEntries] = tail;  
  44.             next[_numEntries] = -1;  
  45.             moveToFront(numEntries);  
  46.         }  
  47.         ++numEntries;  
  48.     }  
  49.     else { // We replace the tail of the list.  
  50.         table.remove(cache[tail].getObjectKey());  
  51.         moveToFront(tail);  
  52.     }  
  53.     cache[head].setObjectValue(value);  
  54.     cache[head].setObjectKey(key);  
  55.     table.put(key, cache[head]);  

這段代碼的邏輯如 LRU算法 的描述一樣,把再次用到的緩存提取到最前面,而每次刪除的都是最后面的元素。

結(jié)論

我們已經(jīng)看到 LFU緩存算法 和 LRU緩存算法的實(shí)現(xiàn)方式,至于如何實(shí)現(xiàn),采用數(shù)組還是 LinkedHashMap,都由你決定,不夠我一般是小的緩存容量用數(shù)組,大的用 LinkedHashMap。

原文鏈接:http://blog.jobbole.com/30940/

責(zé)任編輯:張偉 來(lái)源: 伯樂(lè)在線
相關(guān)推薦

2021-11-30 10:58:52

算法緩存技術(shù)

2019-11-05 14:24:31

緩存雪崩框架

2021-08-05 16:10:03

進(jìn)程緩存緩存服務(wù)Java

2021-12-25 22:28:27

緩存穿透緩存擊穿緩存雪崩

2017-07-13 16:40:16

偽共享緩存行存儲(chǔ)

2025-03-03 07:00:00

2010-10-13 16:44:10

MySQL查詢緩存機(jī)制

2009-06-30 14:08:00

Hibernate緩存

2013-07-29 16:22:21

Android緩存框架

2023-03-10 13:33:00

緩存穿透緩存擊穿緩存雪崩

2019-10-12 14:19:05

Redis數(shù)據(jù)庫(kù)緩存

2022-05-10 08:58:56

CacheHTTP

2011-08-05 15:51:44

MySQL數(shù)據(jù)庫(kù)緩存

2009-09-21 13:49:14

Hiberate3 S

2017-12-27 12:01:39

2021-06-05 09:01:01

Redis緩存雪崩緩存穿透

2022-05-27 07:57:20

緩存穿透緩存雪崩緩存擊穿

2022-03-08 00:07:51

緩存雪崩數(shù)據(jù)庫(kù)

2021-01-20 05:33:03

緩存ReadWriteLo高并發(fā)

2024-11-19 12:00:00

緩存擊穿緩存緩存穿透
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)