fifo(first in first out),先進先出。其實在作業系統的設計理念中很多地方都利用到了先進先出的思想,比如作業排程(先來先服務),為什麼這個原則在很多地方都會用到呢?因為這個原則簡單、且符合人們的慣性思維,具備公平性,並且實現起來簡單,直接使用資料結構中的佇列即可實現。
在fifo cache設計中,核心原則就是:如果乙個資料最先進入快取中,則應該最早淘汰掉。也就是說,當快取滿的時候,應當把最先進入快取的資料給淘汰掉。在fifo cache中應該支援以下操作;
get(key):如果cache中存在該key,則返回對應的value值,否則,返回-1;
set(key,value):如果cache中存在該key,則重置value值;如果不存在該key,則將該key插入到到cache中,若cache已滿,則淘汰最早進入cache的資料。
舉個例子:假如cache大小為3,訪問資料序列為set(1,1),set(2,2),set(3,3),set(4,4),get(2),set(5,5)
則cache中的資料變化為:
(1,1) set(1,1)
(1,1) (2,2) set(2,2)
(1,1) (2,2) (3,3) set(3,3)
(2,2) (3,3) (4,4) set(4,4)
(2,2) (3,3) (4,4) get(2)
(3,3) (4,4) (5,5) set(5,5)
那麼利用什麼資料結構來實現呢?
下面提供一種實現思路:
利用乙個雙向鍊錶儲存資料,當來了新的資料之後便新增到鍊錶末尾,如果cache存滿資料,則把鍊錶頭部資料刪除,然後把新的資料新增到鍊錶末尾。在訪問資料的時候,如果在cache中存在該資料的話,則返回對應的value值;否則返回-1。如果想提高訪問效率,可以利用hashmap來儲存每個key在鍊錶中對應的位置。
lfu(least frequently used)最近最少使用演算法。它是基於「如果乙個資料在最近一段時間內使用次數很少,那麼在將來一段時間內被使用的可能性也很小」的思路。
注意lfu和lru演算法的不同之處,lru的淘汰規則是基於訪問時間,而lfu是基於訪問次數的。舉個簡單的例子:
假設快取大小為3,資料訪問序列為set(2,2),set(1,1),get(2),get(1),get(2),set(3,3),set(4,4),
則在set(4,4)時對於lfu演算法應該淘汰(3,3),而lru應該淘汰(1,1)。
那麼lfu cache應該支援的操作為:
get(key):如果cache中存在該key,則返回對應的value值,否則,返回-1;
set(key,value):如果cache中存在該key,則重置value值;如果不存在該key,則將該key插入到到cache中,若cache已滿,則淘汰最少訪問的資料。
為了能夠淘汰最少使用的資料,因此lfu演算法最簡單的一種設計思路就是 利用乙個陣列儲存 資料項,用hashmap儲存每個資料項在陣列中對應的位置,然後為每個資料項設計乙個訪問頻次,當資料項被命中時,訪問頻次自增,在淘汰的時候淘汰訪問頻次最少的資料。這樣一來的話,在插入資料和訪問資料的時候都能達到o(1)的時間複雜度,在淘汰資料的時候,通過選擇演算法得到應該淘汰的資料項在陣列中的索引,並將該索引位置的內容替換為新來的資料內容即可,這樣的話,淘汰資料的操作時間複雜度為o(n)。
另外還有一種實現思路就是利用 小頂堆+hashmap,小頂堆插入、刪除操作都能達到o(logn)時間複雜度,因此效率相比第一種實現方法更加高效。
如果哪位朋友有更高效的實現方式(比如o(1)時間複雜度),不妨**一下,不勝感激。
lru演算法的設計原則是:如果乙個資料在最近一段時間沒有被訪問到,那麼在將來它被訪問的可能性也很小。也就是說,當限定的空間已存滿資料時,應當把最久沒有被訪問到的資料淘汰。
而用什麼資料結構來實現lru演算法呢?可能大多數人都會想到:用乙個陣列來儲存資料,給每乙個資料項標記乙個訪問時間戳,每次插入新資料項的時候,先把陣列中存在的資料項的時間戳自增,並將新資料項的時間戳置為0並插入到陣列中。每次訪問陣列中的資料項的時候,將被訪問的資料項的時間戳置為0。當陣列空間已滿時,將時間戳最大的資料項淘汰。
這種實現思路很簡單,但是有什麼缺陷呢?需要不停地維護資料項的訪問時間戳,另外,在插入資料、刪除資料以及訪問資料時,時間複雜度都是o(n)。
那麼有沒有更好的實現辦法呢?
那就是利用鍊錶和hashmap。當需要插入新的資料項的時候,如果新資料項在鍊錶中存在(一般稱為命中),則把該節點移到鍊錶頭部,如果不存在,則新建乙個節點,放到鍊錶頭部,若快取滿了,則把鍊錶最後乙個節點刪除即可。在訪問資料的時候,如果資料項在鍊錶中存在,則把該節點移到鍊錶頭部,否則返回-1。這樣一來在鍊錶尾部的節點就是最近最久未訪問的資料項。
總結一下:根據題目的要求,lru cache具備的操作:
1)set(key,value):如果key在hashmap中存在,則先重置對應的value值,然後獲取對應的節點cur,將cur節點從鍊錶刪除,並移動到鍊錶的頭部;若果key在hashmap不存在,則新建乙個節點,並將節點放到鍊錶的頭部。當cache存滿的時候,將鍊錶最後乙個節點刪除即可。
2)get(key):如果key在hashmap中存在,則把對應的節點放到鍊錶頭部,並返回對應的value值;如果不存在,則返回-1。
快取演算法(頁面置換演算法) FIFO LFU LRU
快取演算法 頁面置換演算法 fifo lfu lru 在前一篇文章中通過leetcode的一道題目了解了lru演算法的具體設計思路,下面繼續來 一下另外兩種常見的cache演算法 fifo lfu fifo first in first out 先進先出。其實在作業系統的設計理念中很多地方都利用到了...
快取演算法(頁面置換演算法) FIFO LFU LRU
1.fifo 先進先出 如果乙個資料最先進入快取中,則應該最早淘汰掉。也就是說,當快取滿的時候,應當把最先進入快取的資料給淘汰掉。實現 利用乙個雙向鍊錶儲存資料,當來了新的資料之後便新增到鍊錶末尾,如果cache存滿資料,則把鍊錶頭部資料刪除,然後把新的資料新增到鍊錶末尾。在訪問資料的時候,如果在c...
快取演算法(頁面置換演算法) FIFO LFU LRU
在前一篇文章中通過leetcode的一道題目了解了lru演算法的具體設計思路,下面繼續來 一下另外兩種常見的cache演算法 fifo lfu fifo first in first out 先進先出。其實在作業系統的設計理念中很多地方都利用到了先進先出的思想,比如作業排程 先來先服務 為什麼這個原...