(1)LRU
LRU算法淘汰最长时间没有读或者写过的数据。就以LinkedHashMap为例来说明怎样实现一个LRU算法的数据结构。
首先先看一下LinkedHashMap怎么用的。
package com.demo.bean.zwfz;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
public class LRULinkedMap<K, V> {
/**
* 最大缓存大小
*/
private int cacheSize;
private LinkedHashMap<K, V> cacheMap;
public LRULinkedMap(int cacheSize){
this.cacheSize = cacheSize;
cacheMap = new LinkedHashMap(16, 0.75F, true){
@Override
protected boolean removeEldestEntry(Entry eldest) {
if(cacheSize + 1 == cacheMap.size()){
return true;
}else{
return false;
}
}
};
}
public void put(K key, V value){
cacheMap.put(key, value);
}
public V get(K key){
return cacheMap.get(key);
}
public Collection<Map.Entry<K, V>> getAll(){
return new ArrayList<Map.Entry<K, V>>(cacheMap.entrySet());
}
public static void main(String[] args) {
LRULinkedMap<String, Integer> map = new LRULinkedMap<>(3);
map.put("key1", 1);
map.put("key2", 2);
map.put("key3", 3);
map.put("key8", 99);
for (Map.Entry<String, Integer> e : map.getAll()){
System.out.println(e.getKey()+"====>"+e.getValue());
}
System.out.println("\n");
map.put("key4", 4);
for (Map.Entry<String, Integer> e : map.getAll()){
System.out.println(e.getKey()+"====>"+e.getValue());
}
}
}
这种方法要求缓冲区管理器按照页面最后一次被访问的时间组成一个链表,每次淘汰链表尾部的页面。直觉上,长时间没有读写的页面比那些最近访问过的页面有更小的最近访问的可能性。
(2)LIRS
LRU算法在大多数情况下表现是不错的,但有一个问题:假如某一个查询做了一次全表扫描,将导致缓冲池中的大量数据(可能包含很多很快被访问的热点数据)被替换,从而污染缓冲池。现代数据库一般采用LIRS算法,将缓冲池分为两级,数据首先进入第一级,如果数据在较短的时间内被访问两次或者以上,则成为热点数据进入第二级,每一级内部还是采用LRU替换算法。Oracle数据库中的Touch Count算法和MySQL InnoDB中的替换算法都采用了类似的分级思想。以MySQL InnoDB为例,InnoDB内部的LRU链表分为两部分:新子链表(new sublist)和老子链表(old sublist),默认情况下,前者占5/8,后者占3/8。页面首先插入到老子链表,InnoDB要求页面在老子链表停留时间超过一定值,比如1秒,才有可能被转移到新子链表。当出现全表扫描时,InnoDB将数据页面载入到老子链表,由于数据页面在老子链表中的停留时间不够,不会被转移到新子链表中,这就避免了新子链表中的页面被替换出去的情况。