2020国产成人精品视频,性做久久久久久久久,亚洲国产成人久久综合一区,亚洲影院天堂中文av色

分享

阿里面試,讓我十五分鐘內(nèi)手寫 LRU。。。

 一本正經(jīng)地胡鬧 2021-10-30

你面試的時(shí)候遇見過LRU嗎?

LRU 算法,全稱是Least Recently Used。

翻譯過來就是最近最少使用算法。

這個(gè)算法的思想就是: 如果一個(gè)數(shù)據(jù)在最近一段時(shí)間沒有被訪問到,那么在將來它被訪問的可能性也很小。所以,當(dāng)指定的空間已存滿數(shù)據(jù)時(shí),應(yīng)當(dāng)把最久沒有被訪問到的數(shù)據(jù)淘汰。

聽描述你也知道了,它是一種淘汰算法。

這個(gè)算法也是面試的一個(gè)高頻考點(diǎn)。

有的面試官甚至要求手?jǐn)]一個(gè) LRU 算法出來。

其實(shí)我覺得吧,遇到這種情況也不要慌,你就按照自己的思路寫一個(gè)出來就行。

賭一把,面試官也許自己短時(shí)間內(nèi)都手?jǐn)]不出來一個(gè)無 bug 的 LRU。他也只是檢查幾個(gè)關(guān)鍵點(diǎn)、看看你的代碼風(fēng)格、觀察一下你的解題思路而已。

但其實(shí)大多數(shù)情況下面試場(chǎng)景都是這樣的:

面試官:你知道 LRU 算法嗎?

我:知道,翻譯過來就是最近最少使用算法。其思想是(前面說過,就不復(fù)述了)......

面試官:那你能給我談?wù)勀阌心男┓椒▉韺?shí)現(xiàn) LRU 算法呢?

這個(gè)時(shí)候問的是什么?

問的是:我們都知道這個(gè)算法的思路了,請(qǐng)你按照這個(gè)思路給出一個(gè)可以落地的解決方案。

不用徒手?jǐn)]一個(gè)。

方案一:數(shù)組

如果之前完全沒有接觸過 LRU 算法,僅僅知道其思路。

第一次聽就要求你給一個(gè)實(shí)現(xiàn)方案,那么數(shù)組的方案應(yīng)該是最容易想到的。

假設(shè)我們有一個(gè)定長數(shù)組。數(shù)組中的元素都有一個(gè)標(biāo)記。這個(gè)標(biāo)記可以是時(shí)間戳,也可以是一個(gè)自增的數(shù)字。

我這里用自增的數(shù)字。

每放入一個(gè)元素,就把數(shù)組中已經(jīng)存在的數(shù)據(jù)的標(biāo)記更新一下,進(jìn)行自增。當(dāng)數(shù)組滿了后,就將數(shù)字最大的元素刪除掉。

每訪問一個(gè)元素,就將被訪問的元素的數(shù)字置為 0 。

這不就是 LRU 算法的一個(gè)實(shí)現(xiàn)方案嗎?

按照這個(gè)思路,擼一份七七八八的代碼出來,問題應(yīng)該不大吧?

但是這一種方案的弊端也是很明顯:需要不停地維護(hù)數(shù)組中元素的標(biāo)記。

那么你覺得它的時(shí)間復(fù)雜度是多少?

是的,每次操作都伴隨著一次遍歷數(shù)組修改標(biāo)記的操作,所以時(shí)間復(fù)雜度是 O(n)。

但是這個(gè)方案,面試官肯定是不會(huì)滿意的。因?yàn)?,這不是他心中的標(biāo)準(zhǔn)答案。

也許他都沒想過:你還能給出這種方案呢?

但是它不會(huì)說出來,只會(huì)輕輕的說一句:還有其他的方案嗎?

方案二:鏈表

于是你扣著腦殼想了想。最近最少使用,感覺是需要一個(gè)有序的結(jié)構(gòu)。

我每插入一個(gè)元素的時(shí)候,就追加在數(shù)組的末尾。

等等。

我每訪問一個(gè)元素,也要把被訪問的元素移動(dòng)到數(shù)組的末尾。

這樣最近被用的一定是在最后面的,頭部的就是最近最少使用的。

當(dāng)指定長度被用完了之后,就把頭部元素移除掉就行了。

這是個(gè)什么結(jié)構(gòu)?

這不就是個(gè)鏈表嗎?

維護(hù)一個(gè)有序單鏈表,越靠近鏈表頭部的結(jié)點(diǎn)是越早之前訪問的。

當(dāng)有一個(gè)新的數(shù)據(jù)被訪問時(shí),我們從鏈表頭部開始順序遍歷鏈表。

如果此數(shù)據(jù)之前已經(jīng)被緩存在鏈表中了,我們遍歷得到這個(gè)數(shù)據(jù)的對(duì)應(yīng)結(jié)點(diǎn),并將其從原來的位置刪除,并插入到鏈表尾部。

如果此數(shù)據(jù)沒在緩存鏈表中,怎么辦?

分兩種情況:

  • 如果此時(shí)緩存未滿,可直接在鏈表尾部插入新節(jié)點(diǎn)存儲(chǔ)此數(shù)據(jù);

  • 如果此時(shí)緩存已滿,則刪除鏈表頭部節(jié)點(diǎn),再在鏈表尾部插入新節(jié)點(diǎn)。

你看,這不又是 LRU 算法的一個(gè)實(shí)現(xiàn)方案嗎?

按照這個(gè)思路,擼一份八九不離十的代碼出來,問題應(yīng)該不大吧?

這個(gè)方案比數(shù)組的方案好在哪里呢?

我覺得就是莫名其妙的高級(jí)感,就是看起來就比數(shù)組高級(jí)了一點(diǎn)。

從時(shí)間復(fù)雜度的角度看,因?yàn)殒湵聿迦搿⒉樵兊臅r(shí)候都要遍歷鏈表,查看數(shù)據(jù)是否存在,所以它還是O(n)。

總之,這也不是面試官想要的答案。

當(dāng)你回答出這個(gè)方案之后,面試官也許會(huì)說: 你能不能給我一個(gè)查詢和插入的時(shí)間復(fù)雜度都是O(1)的解決方案?

到這里,如果第一次遇到這題,就得看天分了。

有一說一,如果我之前完全沒有接觸過 LRU 算法,我可以非常自信的說:

方案三:雙向鏈表+哈希表

如果我們想要查詢和插入的時(shí)間復(fù)雜度都是 O(1),那么我們需要一個(gè)滿足下面三個(gè)條件的數(shù)據(jù)結(jié)構(gòu):

  • 1.首先這個(gè)數(shù)據(jù)結(jié)構(gòu)必須是有時(shí)序的,以區(qū)分最近使用的和很久沒有使用的數(shù)據(jù),當(dāng)容量滿了之后,要?jiǎng)h除最久未使用的那個(gè)元素。

  • 2.要在這個(gè)數(shù)據(jù)結(jié)構(gòu)中快速找到某個(gè) key 是否存在,并返回其對(duì)應(yīng)的 value。

  • 3.每次訪問這個(gè)數(shù)據(jù)結(jié)構(gòu)中的某個(gè) key,需要將這個(gè)元素變?yōu)樽罱褂玫摹R簿褪钦f,這個(gè)數(shù)據(jù)結(jié)構(gòu)要支持在任意位置快速插入和刪除元素。

那么,你說什么樣的數(shù)據(jù)結(jié)構(gòu)同時(shí)符合上面的條件呢?

查找快,我們能想到哈希表。但是哈希表的數(shù)據(jù)是亂序的。

有序,我們能想到鏈表,插入、刪除都很快,但是查詢慢。

所以,我們得讓哈希表和鏈表結(jié)合一下,成長一下,形成一個(gè)新的數(shù)據(jù)結(jié)構(gòu),那就是:哈希鏈表,LinkedHashMap。

這個(gè)結(jié)構(gòu)大概長這樣:

借助這個(gè)結(jié)構(gòu),我們?cè)賮矸治鲆幌律厦娴娜齻€(gè)條件:

  • 1.如果每次默認(rèn)從鏈表尾部添加元素,那么顯然越靠近尾部的元素就越是最近使用的。越靠近頭部的元素就是越久未使用的。

  • 2.對(duì)于某一個(gè) key ,可以通過哈希表快速定位到鏈表中的節(jié)點(diǎn),從而取得對(duì)應(yīng)的 value。

  • 3.鏈表顯示是支持在任意位置快速插入和刪除的,修改指針就行。但是單鏈表無非按照索引快速訪問某一個(gè)位置的元素,都是需要遍歷鏈表的,所以這里借助哈希表,可以通過 key,快速的映射到任意一個(gè)鏈表節(jié)點(diǎn),然后進(jìn)行插入和刪除。

這才是面試官想要關(guān)于 LRU 的正確答案。

但是你以為回答到這里就結(jié)束了嗎?

面試官為了確認(rèn)你的掌握程度,還會(huì)追問一下。

那么請(qǐng)問: 為什么這里要用雙鏈表呢,單鏈表為什么不行?

你心里一慌:我靠,這題我也背過。一時(shí)想不起來了。

所以,別只顧著背答案,得理解。

你想啊,我們是不是涉及到刪除元素的操作?

那么鏈表刪除元素除了自己本身的指針信息,還需要什么東西?

是不是還需要前驅(qū)節(jié)點(diǎn)的指針?

那么我們這里要求時(shí)間復(fù)雜度是O(1),所以怎么才能直接獲取到前驅(qū)節(jié)點(diǎn)的指針?

這玩意是不是就得上雙鏈表?

咦,你看在一波靈魂追問中,就得到了答案。

面試官的第二個(gè)問題又隨之而來了: 哈希表里面已經(jīng)保存了 key ,那么鏈表中為什么還要存儲(chǔ) key 和 value 呢,只存入 value 不就行了?

不會(huì),也不要慌,你先分析一波。

剛剛我們說刪除鏈表中的節(jié)點(diǎn),需要借助雙鏈表來實(shí)現(xiàn) O(1)。

刪除了鏈表中的節(jié)點(diǎn),然后呢?

是不是還忘記了什么東西?

是不是還有一個(gè)哈希表忘記操作了?

哈希表是不是也得進(jìn)行對(duì)應(yīng)的刪除操作?

刪除哈希表需要什么東西?

是不是需要 key,才能刪除對(duì)應(yīng)的 value?

這個(gè) key 從哪里來?

是不是只能從鏈表中的結(jié)點(diǎn)里面來?

如果鏈表中的結(jié)點(diǎn),只有 value 沒有 key,那么我們就無法刪除哈希表的 key。那不就完?duì)僮恿藛幔?/p>

又是一波靈魂追問。

所以,你現(xiàn)在知道答案了嗎?

另外在多說一句,有的小伙伴可能會(huì)直接回答借助 LinkedHashMap 來實(shí)現(xiàn)。

我覺得吧,你要是實(shí)在不知道,也可以這樣說。

但是,這個(gè)回答可能是面試官最不想聽到的回答了。

他會(huì)覺得你投機(jī)取巧。

但是呢,實(shí)際開發(fā)中,真正要用的時(shí)候,我們還是用的 LinkedHashMap。

而且更多的實(shí)際情況是,你開發(fā),寫業(yè)務(wù)代碼的時(shí)候,根本就不會(huì)用到 LRU 算法。

你說這個(gè)事情,難受不難受。

好了,你以為到這里面試就結(jié)束了?

天真。

LRU 在 MySQL 中的應(yīng)用

面試官:小伙子剛剛 LRU 回答的不錯(cuò)哈。要不你給我講講,LRU 在 MySQL 中的應(yīng)用?

LRU 在 MySQL 的應(yīng)用就是 Buffer Pool,也就是緩沖池。

它的目的是為了減少磁盤 IO。

緩沖池具體是干啥的,我這里就不展開說了。

你就知道它是一塊連續(xù)的內(nèi)存,默認(rèn)大小 128M,可以進(jìn)行修改。

這一塊連續(xù)的內(nèi)存,被劃分為若干默認(rèn)大小為 16KB 的頁。

既然它是一個(gè) pool,那么必然有滿了的時(shí)候,怎么辦?

就得移除某些頁了,對(duì)吧?

那么問題就來了:移除哪些頁呢?

剛剛說了,它是為了減少磁盤 IO。所以應(yīng)該淘汰掉很久沒有被訪問過的頁。

很久沒有使用,這不就是 LRU 的主場(chǎng)嗎?

但是在 MySQL 里面并不是簡單的使用了 LRU 算法。

因?yàn)?MySQL 里面有一個(gè)預(yù)讀功能。預(yù)讀的出發(fā)點(diǎn)是好的,但是有可能預(yù)讀到并不需要被使用的頁。

這些頁也被放到了鏈表的頭部,容量不夠,導(dǎo)致尾部元素被淘汰。

哦豁,降低命中率了,涼涼。

還有一個(gè)場(chǎng)景是全表掃描的 sql,有可能直接把整個(gè)緩沖池里面的緩沖頁都換了一遍,影響其他查詢語句在緩沖池的命中率。

那么怎么處理這種場(chǎng)景呢?

把 LRU 鏈表分為兩截,一截里面放的是熱數(shù)據(jù),一截里面放的是冷數(shù)據(jù)。

打住,不能再說了。

再說就是另外一篇文章了,點(diǎn)到為止。

你就了解在 MySQL 里面,LRU 是有一個(gè)變種的。

如果你不清楚,建議去學(xué)習(xí)一下哦。

只要你學(xué)的夠快,你就會(huì)被卷的越慢。

LRU 在 Redis 中的應(yīng)用

既然是內(nèi)存淘汰算法,那么我們常用的 Redis 里面必然也有對(duì)應(yīng)的實(shí)現(xiàn)。

Redis 的內(nèi)存淘汰策略有如下幾種:

  • noenviction:默認(rèn)策略。不繼續(xù)執(zhí)行寫請(qǐng)求(DEL 請(qǐng)求可以處理),讀請(qǐng)求可以繼續(xù)進(jìn)行。

  • volatile-lru:從已設(shè)置過期時(shí)間的數(shù)據(jù)集中挑選最近最少使用的數(shù)據(jù)淘汰。沒有設(shè)置過期時(shí)間的 key 不會(huì)被淘汰。

  • volatile-random:從已設(shè)置過期時(shí)間的數(shù)據(jù)集中隨機(jī)選擇數(shù)據(jù)淘汰。

  • volatile-ttl:從已設(shè)置過期時(shí)間的數(shù)據(jù)集中挑選將要過期的數(shù)據(jù)淘汰。

  • allkeys-lru:和 volatile-lru 不同的是,這個(gè)策略要淘汰的 key 對(duì)象是全體的 key 集合。

  • allkeys-random:從所有數(shù)據(jù)集中隨機(jī)選擇數(shù)據(jù)淘汰。

關(guān)于 Redis 中的 LRU 算法,官網(wǎng)上是這樣說的:

https://github.com/redis/redis-doc/blob/master/topics/lru-cache.md

在 Redis 中的 LRU 算法不是嚴(yán)格的 LRU 算法。

Redis 會(huì)嘗試執(zhí)行一個(gè)近似的LRU算法,通過采樣一小部分鍵,然后在采樣鍵中回收最適合的那個(gè),也就是最久沒有被訪問的那個(gè)(with the oldest access time)。

然而,從 Redis 3.0 開始,改善了算法的性能,使得更接近于真實(shí)的 LRU 算法。做法就是維護(hù)了一個(gè)回收候選鍵池。

Redis 的 LRU 算法有一個(gè)非常重要的點(diǎn)就是你可以通過修改下面這個(gè)參數(shù)的配置,自己調(diào)整算法的精度。

maxmemory-samples 5

而上面截圖中,我認(rèn)為最重要的一句話也已經(jīng)標(biāo)志出來了:

The reason why Redis does not use a true LRU implementation is because it costs more memory.

Redis 沒有使用真實(shí)的 LRU 算法的原因是因?yàn)檫@會(huì)消耗更多的內(nèi)存。

然后官網(wǎng)上給了一個(gè)隨機(jī) LRU 算法和嚴(yán)格 LRU 算法的對(duì)比圖:

對(duì)于這個(gè)圖官網(wǎng)是這樣說的:

你可以從圖中看到三種不同的小圓點(diǎn)形成的三個(gè)不同的帶:

  • 淺灰色帶是被回收(被 LRU 算法淘汰)的對(duì)象

  • 灰色帶是沒有被回收的對(duì)象

  • 綠色帶是新添加的對(duì)象

由于 Redis 3.0 對(duì) LRU 算法進(jìn)行了改進(jìn),增加了淘汰池。

所以你可以看到,同樣使用 5 個(gè)采樣點(diǎn),Redis 3.0 表現(xiàn)得比 Redis 2.8 要好。

同時(shí)可以看出,在 Redis 3.0 中使用 10 為采樣大小,近似值已經(jīng)非常接近理論性能。

寫到這里我突然想起了另外一個(gè)面試題。

數(shù)據(jù)庫中有 3000w 的數(shù)據(jù),而 Redis 中只有 100w 數(shù)據(jù),如何保證 Redis 中存放的都是熱點(diǎn)數(shù)據(jù)?

這個(gè)題你說它的考點(diǎn)是什么?

考的就是淘汰策略呀,同志們,只是方式比較隱晦而已。

我們先指定淘汰策略為 allkeys-lru 或者 volatile-lru,然后再計(jì)算一下 100w 數(shù)據(jù)大概占用多少內(nèi)存,根據(jù)算出來的內(nèi)存,限定 Redis 占用的內(nèi)存。

接下來的,就交給 Redis 的淘汰策略了。

搞定。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多