基於LRU演算法的快取池——阿里筆試題
這是一題2011年阿里實習生招聘的筆試題,感覺不錯,拿來給大家分享一下,原題如下:
在進入我的淘寶頁面時,此頁面需要獲取登入的使用者的相關資訊,在訪問量少的情況下,可以採用直接訪問資料庫的方式,但當訪問量太高時,會導致資料庫壓力過高,因此通常採取的方法為將使用者資訊進行快取,在使用者數不多的情況下,這個方案還是提供了很大的幫助的,但使用者數增多了一點後,出現的問題是快取佔了太多的記憶體,而經分析,原因是這些快取中有很多是不訪問的使用者資訊。請寫一段儲存使用者資訊的快取實現程式碼,並實現當快取到達一定大小後,如繼續新增使用者資訊,則將最近不訪問的使用者資訊從快取中踢出。
分析:學過作業系統的都知道實際上題目要我們實現的是一個LRU的演算法,可以考慮用一個雙向連結串列來儲存資料,並用HashTable來記錄資料節點,這裡之所以用HashTable而不用HashMap,是因為HashTable是執行緒安全而HashMap不是執行緒安全的。
下面對get,put,remove方法做一些說明。
1. V get(K key) 方法
先從HashTable中通過nodes.get(key)獲取節點,若為空則直接返回空值;若不為空則將它移到連結串列頭部。
2.void put(K key, V value) 方法
1)先從HashTable中通過nodes.get(key)獲取節點
2)判斷第一步中取到的節點是否為空。若為空則進入3),否則進入4)
3)判斷快取容器是否已滿。若容器已滿則從HashTable和連結串列中移除該節點;否則currentSize加1。完成後新增一個節點
4)設定節點的key和value,並將節點移動至連結串列頭部,然後存入HashTable。
3.V remove(K key) 方法
1)先從HashTable中通過nodes.remove(key)並獲取移除的節點
2) 判斷第一步中移除的節點是否為空。若為空則直接返回空值;否則從連結串列中移除該節點,並返回移除的value。
原始碼:
LRUCache.java
import java.util.Hashtable; /** * * @author caolijie LRU 快取 * @param <K> * @param <V> */ public class LRUCache<K, V> { private int cacheSize; // 快取池大小 private Hashtable nodes;// 快取容器 private int currentSize;// 當前大小 private CacheNode<K, V> first;// 連結串列頭 private CacheNode<K, V> last;// 連結串列尾 /** * 連結串列節點 * * @author caolijie * */ class CacheNode<K, V> { CacheNode<K, V> prev;// 前一節點 CacheNode<K, V> next;// 後一節點 V value;// 值 K key;// 鍵 public CacheNode() { } } public <K, V> LRUCache(int capacity) throws IllegalAccessException { if (capacity <= 0) { throw new IllegalAccessException( "capacity must be positive integer."); } currentSize = 0; cacheSize = capacity; nodes = new Hashtable<K, CacheNode>(capacity); // 快取容器 } /** * 獲取快取中物件 * * @param key * @return */ public synchronized V get(K key) { CacheNode<K, V> node = (CacheNode<K, V>) nodes.get(key); if (node != null) { moveToHead(node); return node.value; } else { return null; } } /** * 新增快取 * * @param key * @param value */ public synchronized void put(K key, V value) { CacheNode<K, V> node = (CacheNode<K, V>) nodes.get(key); if (node == null) {// 沒有命中 // 快取容器是否已經超過大小. if (currentSize >= cacheSize) { if (last != null)// 將最少使用的刪除 nodes.remove(last.key);// 從快取容器中移除 removeLast();// 從雙向連結串列中移除最後一項 } else { currentSize++; } node = new CacheNode<K, V>(); } node.value = value; node.key = key; // 將最新使用的節點放到連結串列頭,表示最新使用的. moveToHead(node); nodes.put(key, node); } /** * 將快取刪除 * * @param key * @return V */ public synchronized V remove(K key) { CacheNode<K, V> node = (CacheNode<K, V>) nodes.get(key); if (node != null) { if (node.prev != null) { node.prev.next = node.next; } if (node.next != null) { node.next.prev = node.prev; } if (last == node) last = node.prev; if (first == node) first = node.next; } return node.value; } public synchronized void clear() { first = null; last = null; } /** * 刪除連結串列尾部節點 表示 刪除最少使用的快取物件 */ private synchronized void removeLast() { // 連結串列尾不為空,則將連結串列尾指向null. 刪除連表尾(刪除最少使用的快取物件) if (last != null) { if (last.prev != null) last.prev.next = null; else first = null;// 連結串列中只有一個元素 last = last.prev; } } /** * 移動到連結串列頭,表示這個節點是最新使用過的 * * @param node */ private synchronized void moveToHead(CacheNode<K, V> node) { if (node == first) return; if (node.prev != null) node.prev.next = node.next; if (node.next != null) node.next.prev = node.prev; if (last == node) last = node.prev; if (first != null) { node.next = first; first.prev = node; } first = node; node.prev = null; if (last == null) last = first; } }
LRUCacheTest.java
public class LRUCacheTest {
public static void main(String[] args) throws IllegalAccessException {
LRUCache<String, Integer> cache = new LRUCache<String, Integer>(100);
for (int i = 0; i < 200; i++) {
cache.put("" + i, i);
}
for (int i = 0; i < 200; i++) {
System.out.print(cache.get("" + i) + " ");
}
}
}