严格的lru算法
1.基于读 更新时间, 2.并且要消除偶尔读对整体数据的影响. 也就是说要考虑 最近x小时内读的次数. 和 最近读的次数.
实现上, 需要有个队列时刻维护好这个顺序关系. 但是redis没有这样实现, 避免内存浪费.
jdk这样实现了, 而且有个AccessOrder 来实现按读还是按写排序, AccessOrder=true按读.
redis的lru原理
抽样算法 , 结合 pooling 缓存池技术
那我能不能在下一轮移除Key时,利用好上一轮知晓的一些信息?
However if you think at this algorithm across its executions, you can see how we are trashing a lot of interesting data. Maybe when sampling the N keys, we encounter a lot of good candidates, but we then just evict the best, and start from scratch again the next cycle.
start from scratch太傻了。于是Redis又做出了改进:采用缓冲池(pooling)
当每一轮移除Key时,拿到了这个N个Key的idle time,如果它的idle time比 pool 里面的 Key的idle time还要大,就把它添加到pool里面去。这样一来,每次移除的Key并不仅仅是随机选择的N个Key里面最大的,而且还是pool里面idle time最大的,并且:pool 里面的Key是经过多轮比较筛选的,它的idle time 在概率上比随机获取的Key的idle time要大,可以这么理解:pool 里面的Key 保留了"历史经验信息"。
java 里的lru
LinkedHashMap removeEldestEntry 基于create/read的lru算法. 不是基于读的lru算法.
accessOrder=true时
例子
public class Test {
public static class LruMap extends LinkedHashMap{
public LruMap(){
super(16,0.75f,true);
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
int size = size();
return size >2;
}
}
public static void main(String[] args) throws InterruptedException {
Map map=new LruMap();
map.put("1","1");
map.put("2","2");
Object o = map.get("1");
System.out.println("after read key=1,value="+o+", map="+map.entrySet());
map.put("3",3);
System.out.println("after put key=3,value=3, map="+map.entrySet());
}
}
输出结果
after read key=1,value=1, map=[2=2, 1=1]
after put key=3,value=3, map=[1=1, 3=3]
源代码解读
void afterNodeAccess(Node<K,V> e) { // move node to last LinkedHashMap.Entry<K,V> last; if (accessOrder && (last = tail) != e) { LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; p.after = null; //将e从中间抽出来, 步骤1: 把e的a节点(after节点) 设置到e的b节点(before节点)上after上 if (b == null) head = a; else b.after = a; //将e从中间抽出来,步骤2: 把e的e的b节点(before节点)上 设置到a节点(after节点) 的before上 if (a != null) a.before = b; else last = b; // 将e节点放置到最末尾, 代表最新读取. if (last == null) head = p; else { p.before = last; last.after = p; } tail = p; ++modCount; } }
accorder=false时,默认
public class Test {
public static class LruMap extends LinkedHashMap{
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
int size = size();
return size >2;
}
}
public static void main(String[] args) throws InterruptedException {
Map map=new LruMap();
map.put("1","1");
map.put("2","2");
Object o = map.get("1");
System.out.println("after read key=1,value="+o+", map="+map.entrySet());
map.put("3",3);
System.out.println("after put key=3,value=3, map="+map.entrySet());
}
}
after read key=1 ,value=1, map=[1=1, 2=2]
after put key=3, value=3, map=[2=2, 3=3]
gava中的实现
剖析LinkedHashMap 和 LRU实现的种种_黄老师-的博客-CSDN博客_java lru
Guava LRU的具体实现
在Guava Cache的LRU实现中,它的双向链表并不是全局的。而是每个Segment(ConcurrentHashMap中的概念)中都有。
一共涉及到三个Queue其中包括:AccessQueue和WriteQueue,以及RecentQueue。其中AccessQueue和WriteQueue就是双向链表;而RecentQueue才是真正的Queue,它就是CocurrentLinkedQueue。
接下来我们简单分析下Guava Cache是如何通过这三个Queue来实现的LRU。
有界队列实现
需要有个淘汰机制. 利用linkedHashMap可以实现