您的位置:首页 > 其它

基于LRU算法的缓存池——阿里笔试题

2014-04-27 21:47 148 查看
这是一题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) + "  ");
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: