引子:
我们平时总会有一个电话本记录所有朋友的电话,但是,如果有朋友经常联系,那些朋友的电话号码不用翻电话本我们也能记住,但是,如果长时间没有联系了,要再次联系那位朋友的时候,我们又不得不求助电话本,但是,通过电话本查找还是很费时间的。但是,我们大脑能够记住的东西是一定的,我们只能记住自己最熟悉的,而长时间不熟悉的自然就忘记了。
其实,计算机也用到了同样的一个概念,我们用缓存来存放以前读取的数据,而不是直接丢掉,这样,再次读取的时候,可以直接在缓存里面取,而不用再重新查找一遍,这样系统的反应能力会有很大提高。但是,当我们读取的个数特别大的时候,我们不可能把所有已经读取的数据都放在缓存里,毕竟内存大小是一定的,我们一般把最近常读取的放在缓存里(相当于我们把最近联系的朋友的姓名和电话放在大脑里一样)。现在,我们就来研究这样一种缓存机制。
LRU Cache:
LRU缓存利用了这样的一种思想。LRU是Least Recently Used 的缩写,翻译过来就是“最不常使用”,也就是说,LRU缓存把最不常使用的数据移除,让给最新读取的数据。大多数情况下,最常读取的,也是读取次数最多的,所以,利用LRU缓存,我们能够提高系统的performance. LRU cache是非常频繁出现的一道面试题,一般来讲,当我们问到这道题时,面试官往往想得到的答案是利用 doubly linked list + hashtable 实现 LRU Cache, 那么我们现在就来讲一讲如何利用doubly linked list + hashtable实现LRU Cache的。
对于LRU cache,往往会有以下要求:
1. 假设Cache里面的 entry 都是按照序列保存的,那么,对于新的entry,我们把它放置在最前面。
2. 如果一个entry已经存在,我们再次访问到该entry的时候,我们需要把它放在cache的最前面。
3. 当cache满了的时候,需要把最后一个entry 从cache里面移除出去,然后再往里插入 entry。
4. 以上所有的操作复杂度必须为 O(1)。
对于操作复杂度,一旦看到要求为O(1), 一般我们都会立刻想到 hashtable, 所以,为了实现“顺序”的要求,我们需要有一个链表来连接所有的entry. 所以,在实现时,我们将Cache的所有 entry 都用doubly linked list 连接起来,当一个 entry 被命中之后,就将通过调整链表的指向,将该位置调整到链表头的位置,新加入的Cache直接加到链表头中。这样,在多次进行Cache操作后,最近被命中的,就会被向链表头方向移动,而没有命中的,而想链表后面移动,链表尾则表示最近最少使用的Cache。当需要替换内容时候,链表的最后位置就是最少被命中的位置,我们只需要淘汰链表最后的部分即可。
所谓LRU就是最近最少被使用的意思,LRUCache的目的就是当缓存中的数据量达到一定的程度时,添加新的数据会将最近最少被使用的旧数据删除,以保障Cache中的数据都是经常需要的数据,提高命中率。下面是一个基于linkedhashmap的LRUCache的简单实现,关键是重写LinkedHashMap的removeEldestEntry方法,在LinkedHashMap中该方法默认返回false(LRUCache本身未考虑线程安全的问题)
package test1;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Created by Administrator on 15-1-21.
*/
public class BasicLRUCache<K, V> {
private LinkedHashMap<K, V> map;
private int cacheSize;
static final int DEFAULT_CACHE_SIZE = 2;
public BasicLRUCache(){
this(DEFAULT_CACHE_SIZE);
}
public BasicLRUCache(int cacheSize){
this.cacheSize = cacheSize;
int hashTableSize = (int)Math.ceil(cacheSize/0.75f) + 1;
map = new LinkedHashMap<K, V>(hashTableSize, 0.75f, true){
private static final long serialVersionUID = 1L;
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
System.out.println("size=" + size() + " cacheSize=" + BasicLRUCache.this.cacheSize);
return size() > BasicLRUCache.this.cacheSize;
}
};
}
public V put(K key, V value){
return map.put(key, value);
}
public V get(Object key){
return map.get(key);
}
public static void main(String[] args){
BasicLRUCache<String, String> lruCache = new BasicLRUCache<String, String>(3);
lruCache.put("1", "1");
lruCache.put("2", "2");
lruCache.put("3", "3");
lruCache.put("4", "4");
System.out.println(lruCache.get("1"));
System.out.println(lruCache.get("2"));
System.out.println(lruCache.get("3"));
System.out.println(lruCache.get("4"));
}
}
"C:\Program Files\Java\jdk1.7.0_67\bin\java" -Didea.launcher.port=7533 "-Didea.launcher.bin.path=D:\Program Files\JetBrains\IntelliJ IDEA 13.1.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.7.0_67\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\jce.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\jfxrt.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\resources.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\rt.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\ext\access-bridge.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.7.0_67;D:\IdeaProject\test\dubbo-consumer;D:\IdeaProject\bppfRefactor\bpep-portal;D:\IdeaProject\bppfRefactor\test\target\classes;D:\Program Files\JetBrains\IntelliJ IDEA 13.1.3\lib\idea_rt.jar" com.intellij.rt.execution.application.AppMain test1.BasicLRUCache
size=1 cacheSize=3
size=2 cacheSize=3
size=3 cacheSize=3
size=4 cacheSize=3
null
2
3
4
Process finished with exit code 0