1. 程式人生 > >基於LRU演算法的快取池——阿里筆試題

基於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) + "  ");
		}
	}
}