缓存置换策略-LRU算法
LRU算法
LRU算法定义:
LRU算法是指最近最少使用算法,意思是LRU认为最近使用过的数据,将来被访问的概率会大,最近没有被访问的数据意味着以后刚问的概率小。
为何要用LRU算法:
1、我们的存储空间是有限的,当存储空间满了之后,要删除哪些数据呢,才能会时缓存的命中率高一些呢
2、LRU算法还是比较简单的。
算法:
最常见的算法是使用一个链表来实现
1)将新数据插入到表头
2)每当缓存命中时,将数据移动到表头
3)当l存储空间满了的时候将链表尾部的数据删除
缺点:链表实现,需要遍历链表找到命中的数据
java中最简单的LRU算法实现,就是利用jdk的LinkedHashMap。 LinkedHashMap底层就是用的HashMap加双链表实现的,而且本身已经实现了按照访问顺序的存储。此外,LinkedHashMap中本身就实现了一个方法removeEldestEntry用于判断是否需要移除最不常读取的数,方法默认是直接返回false,不会移除元素,所以需要重写该方法。即当缓存满后就移除最不常用的数。(通过覆盖这个方法,加入一定的条件,满足条件返回true。当put进新的值方法返回true时,便移除该map中最老的键和值。)
算法分析:
偶发性的、周期性的批量操作(可能不是热点数据)会使临时数据涌入缓存,挤出热点数据,导致LRU热点命中率急剧下降,缓存污染情况比较严重。
public class LRU<K,V> { private static final float hashLoadFactory = 0.75f; private LinkedHashMap<K,V> map; private int cacheSize; public LRU(int cacheSize) { this.cacheSize = cacheSize; int capacity = (int)Math.ceil(cacheSize / hashLoadFactory) + 1; map = new LinkedHashMap<K,V>(capacity, hashLoadFactory, true){ private static final long serialVersionUID = 1; @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > LRU.this.cacheSize; } }; } public synchronized V get(K key) { return map.get(key); } public synchronized void put(K key, V value) { map.put(key, value); } public synchronized void clear() { map.clear(); } public synchronized int usedSize() { return map.size(); } public void print() { for (Map.Entry<K, V> entry : map.entrySet()) { System.out.print(entry.getValue() + "--"); } System.out.println(); } }
LRU-K算法
LRU-K算法定义:
LRU-K中的K代表最近使用的次数,因此LRU可以认为是LRU-1。LRU-K的主要目的是为了解决LRU算法“缓存污染”的问题,其核心思想是将“最近使用过1次”的判断标准扩展为“最近使用过K次”。
算法实现:
相比LRU,LRU-K需要多维护一个队列,用于记录所有缓存数据被访问的历史。只有当数据的访问次数达到K次的时候,才将数据放入缓存。当需要淘汰数据时,LRU-K会淘汰第K次访问时间距当前时间最大的数据。详细实现如下:
1. 数据第一次被访问,加入到访问历史列表;
2. 如果数据在访问历史列表里后没有达到K次访问,则按照一定规则(FIFO,LRU)淘汰;
3. 当访问历史队列中的数据访问次数达到K次后,将数据索引从历史队列删除,将数据移到缓存队列中,并缓存此数据,缓存队列重新按照时间排序;
4. 缓存数据队列中被再次访问后,重新排序;
5. 需要淘汰数据时,淘汰缓存队列中排在末尾的数据,即:淘汰“倒数第K次访问离现在最久”的数据。