設計一個Cache系統 【雙向鏈表+哈希表】

轉自:http://blog.csdn.net/yangcs2009/article/details/38351573

今天去面試,面試官讓我設計一個cache系統,要求保證最近使用的數據不能被移除出cache,也就是每次添加一個cache項的時候,把最舊的cache項移除出去。

    我只記得操作系統裏貌似有個差不多的cache算法,記不起名字來,更別提數據結構了。一開始我執着於用一種數據結構來實現,可是每說出一種,都被面試官指出這種方式的不足。最後終於開竅了,想出了 哈希表+雙向鏈表 的方法。當時也不知道到底對不對,感覺這種實現還過的去吧,查詢和增加cache項複雜度都是O(1)。

    回來後一查,原來就是LRU(Least Recent Used)算法。好到事實上就是採用了哈希表+雙向鏈表的組合拳法。文末附上一篇別人寫的博文。

   另外,我發現Java1.4中有一個LinkedHashMap,就是哈希表+雙鏈表,有一個removeEldestEntry方法,裏面沒有實現,override這個方法後很容易實現FIFO的Cache,對其它幾個方法也override後想必實現LRU也不難。

 http://blog.sina.com.cn/s/blog_567842410100nf1g.html

Cache(高速緩存), 一個在計算機中幾乎隨時接觸的概念。CPU中Cache能極大提高存取數據和指令的時間,讓整個存儲器(Cache+內存)既有Cache的高速度,又能有內存的大容量;操作系統中的內存page中使用的Cache能使得頻繁讀取的內存磁盤文件較少的被置換出內存,從而提高訪問速度;數據庫中數據查詢也用到Cache來提高效率;即便是Powerbuilder的DataWindow數據處理也用到了Cache的類似設計。Cache的算法設計常見的有FIFO(first in first out)和LRU(least recently used),FIFO對CPU的指令序列非常有效,但更多的,對於Memory或者磁盤文件的這種Cache,LRU就更有效多了。FIFO的算法設計很簡單明瞭,就不做討論,在此只是對LRU展開。

Cache中的存儲空間被分成若干塊,每一塊對應內存或者磁盤文件的一段數據(當然也可以是指令數據),形成這種映射關係,當Cache中的存儲塊被用完,而需要把新的數據Load進Cache的時候,我們就需要設計一種良好的算法來完成數據塊的替換。LRU的思想是基於“最近用到的數據被重用的概率比較早用到的大的多”這個設計規則來實現的。Cache中的所有塊位置都用雙向鏈表鏈接起來,當一個位置被命中後,就將通過調整鏈表的指向將該位置調整到鏈表的頭位置,新加入的內容直接放在鏈表的頭上。這樣,在進行過多次查找操作後,最近被命中過的內容就向鏈表的頭移動,而沒有被命中的內容就向鏈表的後面移動。當需要替換時,鏈表最後的位置就是最近最少被命中的位置,我們只需要將新的內容放在鏈表前面,淘汰鏈表最後的位置就是想了。

對於雙向鏈表的使用,基於兩個考慮。首先是Cache中塊的命中可能是隨機的,和Load進來的順序無關,所以我們需要用鏈表這種結構來保存位置隊列,使得其可以靈活的調整相互間的次序。其次,雙向鏈表使得在知道一個位置的情況下可以很迅速的移到其他的地方,時間複雜度爲O(1)。

查找一個鏈表中元素的時間複雜度是O(n),每次命中的時候,我們就需要花費O(n)的時間來進行查找,如果不添加其他的數據結構,這個就是我們能實現的最高效率了。目前看來,整個算法的瓶頸就是在查找這裏了,怎麼樣才能提高查找的效率呢?Hash表,對,就是它,數據結構中之所以有它,就是因爲它的查找時間複雜度是O(1)。

梳理一下思路:對於Cache的每個位置,我們設計一個數據結構來儲存Cache塊的內容,並實現一個雙向鏈表,其中屬性next和prev是雙向鏈表的兩個指針,key用於存儲對象的鍵值,value用於存儲要cache塊對象本身,然後用Hash表來查找具體被命中的Cache塊。剩下的就是寫Code的事了:我們使用一個hashmap作爲cache,用hashmap的檢索機制來實現cache查找;並用head和last兩個屬性來記錄鏈表的頭和尾。並提供putEntry(),getEntry()方法來操作該cache。

本文出自 “木又寸的技術博客” 博客,請務必保留此出處http://jianshusoft.blog.51cto.com/2380869/666653


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章