LRU 最近最少使用 資料快取機制的實現

2021-10-10 00:12:24 字數 3927 閱讀 5093

面試常問

1.為什麼會需要資料的快取?(分頁儲存機制)

(1)缺頁中斷:我們知道在作業系統中,資料儲存是使用分頁儲存的機制,乙個程序可以對應乙個頁表,頁表中有很多頁,也就是當乙個程序對應很多頁時,執行程序時並不是所有頁都裝入記憶體中(當然要考慮記憶體開銷和io阻塞),會將部分裝入記憶體。

(2)當需要的那頁不存在記憶體中時,這時將發生缺頁中斷,需要把那頁從外存中調入記憶體中;

ps:

(2) 塊表: 頁的大小相同,記憶體中的塊與頁大小相同

2.怎麼進行頁面的置換呢?(頁面排程-置換演算法)

調頁演算法:

先進先出,最佳頁面置換演算法(opt);

最近最久未使用(nru);

最近最少使 用置換演算法(lru);

先進先出演算法(fifo)會導致 baley 問題;

抖動,頁面在記憶體與外存中的頻繁調頁;

3.這體現了程式區域性性原理,時間區域性性、空間區域性性;4.lru的設計與實現:我們先看需要完成的功能:具體可以看leetcode t.146

lru快取設計

(1)設計和實現乙個 lru (最近最少使用) 快取機制。它應該支援以下操作: 獲取資料 get 和 寫入資料 put 。

(2)獲取資料 get(key) - 如果關鍵字 (key) 存在於快取中,則獲取關鍵字的值(總是正數),否則返回 -1。

(3)寫入資料 put(key, value) - 如果關鍵字已經存在,則變更其資料值;如果關鍵字不存在,則插入該組「關鍵字/值」。當快取容量達到上限時,它應該在寫入新資料之前刪除最久未使用的資料值,從而為新的資料值留出空間。

這裡最關鍵的要求就是:是否可以在 o(1) 時間複雜度內完成這兩種操作?

我們來一步分析下:

1)

a. 用乙個陣列來儲存資料,給每乙個資料項標記乙個訪問時間戳,每次插入新資料項的時候,先把陣列中存在的資料的時間戳自增,並將新資料時間戳置為 0 插入到陣列中。每次訪問陣列中的資料項的時候,將被訪問的資料項時間戳置為 0。當陣列空間已經滿時,將時間戳最大的資料項淘汰;

b. 時間複雜度分析:由於使用的陣列,增刪查都是順序,資料的查詢,刪除和插入總體來說是o(n)情況下的。而且資料量大的情況下很慢。

空間複雜度分析:使用了乙個陣列,為乙個隨被處理資料量n的大小而改變的常量o(n)而且陣列容量的擴容也是個很大的問題

2)

a.利用乙個鍊錶來實現,每次新插入資料的時候將新資料插入到鍊錶頭部;每次快取命中,則將資料移動到鍊錶頭部;那麼當鍊表滿時,就將鍊錶尾部的資料丟棄;

b. 時間複雜度分析:由於使用的鍊錶,增刪的話就優化成了o(1),但是鍊錶的查詢還是線性的,也就是o(n)

空間複雜度分析:用到乙個鍊錶,隨被處理資料量n的大小而改變o(n),同時鍊錶能有效利用不連續的記憶體空間,存放的資料數量增加

3)

a.於是我們對方法(2)再進一步優化,需要將查詢做到o(1),很自然的想到了hash_map:利用鍊錶和 hashmap。當需要插入新的資料項 的時候,如果新資料命中,則把該節點放到鍊錶頭部,如果不存在,則將新資料放在鍊錶頭部。若快取滿了,則將鍊錶尾部的節點刪除。

b.時間複雜度分析:增刪查都是o(1)

空間複雜度分析: 用到了乙個雙端鍊錶,乙個unordered_map,隨被處理資料量n的大小而改變,所以 應該是 2*o(n);

設計lru的資料結構和複雜度分析完成了,我們就具體設計一下:

/*(1)主要實現就是通過乙個雙端佇列和hashmap;

(2)首先是想把資料(key - value)存放在乙個陣列中,想到是用vector容器還是佇列,由於需要插入和刪除 資料,vector的話就需要資料的移動,但是用雙端佇列的話,就能實現o(1)的複雜度:自然儲存的是 pair資料,這樣當訪問新的資料的時候,就把新的資料插入到隊頭,(也就是最近使用的存對 頭,最久未使用的放隊尾)

(3)這裡就涉及到查詢資料是否存在與佇列中。為了實現o(1)的時間複雜度,很自然的想到hash_map,首先它 的k就是資料的key,v的話需要考慮一下,由於是根據map在list中找,就需要此key在list中的位置,也就 是下標索引,由於是不連續的空間,所用用list的迭代器定位它的位置

(4)下面我們來看具體實現,

首先:lrucache(int capacity),通過建構函式實現

容量capacity,決定了lru快取的資料容量

然後:資料的寫入操作 void put(int key, int value)

put(key, value) - 如果關鍵字已經存在,則變更其資料值----首先在hash_map中尋找是否存在,

a.如果存在,那這個資料是最近使用的,在list中找到對應的pair(key,原始_value),用臨時變數 儲存下,然年刪除,再把對應的新的pair(key,value)插入在隊頭。

這裡需要注意的點是:變更hash_map中key對應的list下標索引位置(應該是begin()位置)

b.如果關鍵字不存在,則直接在隊頭插入該pair(key,value),在hash_map中儲存key 和 下標索引 begin()。

c.當快取容量達到上限時,它應該在寫入新資料之前刪除最久未使用的資料值,從而為新的資料值 留出空間。也就是在list的隊尾刪除之,相對應的也在hash_map中刪除這個最久未使用的資料。然 後在隊頭插入新的資料,hash_map記錄資料。

d.至此,lru的資料寫入操作就完成了

其次:資料的讀操作 int get(int key)

獲取資料 get(key) - 如果關鍵字 (key) 存在於快取中,則獲取關鍵字的值(總是正數),否則 返回 -1。

a.首先根據查詢的key,同樣在hash_map中查詢其位置,如果不存在(我認為hash_map和list除了 剛啟動時是空的,後續操作是保持不空的),就直接返回-1;如果存在,就從list中返回key對應 的資料,同時變更位置(成為最近使用的資料,放隊頭)

b.至此,lru的讀操作就完成了

最後: lru (最近最少使用) 快取機制完成。

*/class

lrucache

intget

(int key)

}void

put(

int key,

int value)

else

//put (key,value)

//put the new(key,value)

cache.

push_front

(make_pair

(key,value));

mp[key]

= cache.

begin()

;}}}

;/**

* your lrucache object will be instantiated and called as such:

* lrucache* obj = new lrucache(capacity);

* int param_1 = obj->get(key);

* obj->put(key,value);

*/

還是想說一下,快取機制lru,以及lfu的設計與實現在面試中重要的一批,一定要掌握,加油!

如何實現LRU(最近最少使用)快取淘汰演算法?

我們維護乙個有序單鏈表,越靠近鍊錶尾部的結點是越早之前訪問的。當有乙個新的資料被訪問時,我們從煉表頭開始順序遍歷鍊錶。如果此資料之前已經被快取在鍊錶中了,我們遍歷得到這個資料對應的結點,並將其從原來的位置刪除,然後再插入到鍊錶的頭部。如果此資料沒有在快取鍊錶中,又可以分為兩種情況 如果此時快取未滿,...

自適應Lru(最近最少使用)演算法

在快取管理演算法中,lru 幾乎是公認的最優的演算法。然而它也有一些缺陷,主要是因為 它假定對實體的訪問有區域性特性。當訪問模式沒有區域性特性的時候,它就會退化為fifo 先進先出 演算法。在我寫乙個檔案系統的實現時,這種現象很讓我頭疼,因為很多時候,對乙個檔案的訪問大多是順序的,前面讀取過的內容幾...

什麼是LRU(最近最少使用)演算法?

lru least recently used 最近最少使用。是一種 記憶體管理 演算法。lru演算法基於一種假設 長期不被使用的資料,在未來被用到的機率也不大。因此,當資料所佔記憶體達到一定閾值時,要移除掉最近最少使用的資料。lru演算法使用了一種有趣的資料結構,叫做 雜湊鍊錶 1 雜湊表 是由若...