缓存失效算法

先来先淘汰(FIFO)

First In First Out,先来先淘汰。这种算法在每一次新数据插入时,如果队列已满,则将最早插入的数据移除。

可以方便的借助LinkedList来实现

package cache;

import java.util.Iterator;
import java.util.LinkedList;

public class FIFO {

	LinkedList<Integer> fifo = new LinkedList<Integer>();
	int size = 3;
	
	private void print() {
		System.out.println(this.fifo);

	}
	
	public void add(int i) {
		fifo.addFirst(i);
		if(fifo.size() > size) {
			fifo.removeLast();
		}
		print();
	}
	
	private void read(int i) {
		
		Iterator<Integer> iterator = fifo.iterator();
		while(iterator.hasNext()) {
			int j = iterator.next();
			if(i == j) {
				System.out.println("find it!");
				print();
				return;
			}
		}
		System.out.println("not found");
		print();
	}
	
	public static void main(String[] args) {
		FIFO fifo = new FIFO();
		fifo.add(1);
		fifo.add(2);
		fifo.add(3);
		fifo.read(1);
		fifo.read(100);
	}
}

最久未用淘汰(LRU)

LRU全称是Least Recently Used,即淘汰最后一次使用时间最久远的数值。FIFO非常的粗暴,不管有没有用到,直接踢掉时间久的元素。而LRU认为,最近频繁使用过的数据,将来也很大程度上会被频繁用到,故而淘汰那些懒惰的数据。LinkedHashMap,数组,链表均可实现LRU,下面仍然以链表为例:新加入的数据放在头部,最近访问的,也移到头部,空间满时,将尾部元素删除

package cache;

import java.util.Iterator;
import java.util.LinkedList;

public class LRU {

	LinkedList<Integer> lru = new LinkedList<Integer>();
	int size = 3;
	
	private void print() {
		System.out.println(this.lru);
	}
	
	public void add(int i) {
		lru.addFirst(i);
		if(lru.size() > size) {
			lru.removeLast();
		}
		print();
	}
	
	private void read(int i) {
		
		Iterator<Integer> iterator = lru.iterator();
		int index = 0;
		
		while(iterator.hasNext()) {
			int j = iterator.next();
			if(i == j) {
				System.out.println("find it!");
				lru.remove(index);
				lru.addFirst(j);
				
				print();
				return;
			}
			index++;
		}
		System.out.println("not found");
		print();
	}
	
	public static void main(String[] args) {
		LRU lru = new LRU();
		lru.add(1);
		lru.add(2);
		lru.add(3);
		lru.read(2);
		lru.read(100);
	}
}

最近最少使用(LFU)

Least Frequently Used,即最近最少使用。它要淘汰的是最近一段时间内,使用次数最少的值。可以认为比LRU多了一重判断。LFU需要时间和次数两个维度的参考指标。需要注意的是,两个维度就可能涉及到同一时间段内,访问次数相同的情况,就必须内置一个计数器和一个队列,计数器算数,队列放置相同计数时的访问时间。

package cache;

public class Dto implements Comparable<Dto> {

	private Integer key;
	private int count;
	private long lastTime;
	
	public Dto(Integer key, int count, long lastTime) {
		this.key = key;
		this.count = count;
		this.lastTime = lastTime;
	}

	@Override
	public int compareTo(Dto o) {
		// TODO Auto-generated method stub
		int compare = Integer.compare(this.count, o.count);
		return compare == 0 ?Long.compare(this.lastTime, o.lastTime) :compare;
	}

	@Override
	public String toString() {
		return String.format("[key=%s,count=%s,lastTime=%s]",key,count,lastTime);
	}
	
	public Integer getKey() {
		return key;
	}
	public void setKey(Integer key) {
		this.key = key;
	}
	public int getCount() {
		return count;
	}
	public void setCount(int count) {
		this.count = count;
	}
	public long getLastTime() {
		return lastTime;
	}
	public void setLastTime(long lastTime) {
		this.lastTime = lastTime;
	}
}
package cache;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class LFU {

	private final int size =3;
	
	private Map<Integer, Integer> cache = new HashMap<>();
	private Map<Integer, Dto> count = new HashMap<>();
	
	private void print() {
		System.out.println("cache="+cache);
		System.out.println("count="+count);
	}
	
	private void removeElement() {
		Dto dto = Collections.min(count.values());
		cache.remove(dto.getKey());
		count.remove(dto.getKey());
	}
	
	private void addCount(Integer key) {
		Dto Dto = count.get(key);
		Dto.setCount(Dto.getCount()+1);
		Dto.setLastTime(System.currentTimeMillis());
	}
	
	
	
	private void put(Integer key, Integer value) {
		Integer v = cache.get(key);
		if(v == null) {
			if(cache.size() == size) {
				removeElement();
			}
			count.put(key, new Dto(key, 1, System.currentTimeMillis()));
		}else {
			addCount(key);
		}
		cache.put(key, value);
	}
	
	public Integer get(Integer key) {
		Integer value = cache.get(key);
		if(value != null) {
			addCount(key);
			return value;
		}
		return null;
	}
	
	public static void main(String[] args) {
		LFU lfu = new LFU();
		
		System.out.println("add 1-3:");
		lfu.put(1, 1);
		lfu.put(2, 2);
		lfu.put(3, 3);
		lfu.print();
		
		System.out.println("1,2有访问,3没有,加入4,淘汰3");
		lfu.get(1);
		lfu.get(2);
		lfu.print();
		lfu.put(4, 4);
		lfu.print();
		
		System.out.println("2=3次,1,4=2次,但是4加入较晚,再加入5时淘汰1");
		lfu.get(2);
		lfu.get(4);
		lfu.print();
		System.out.println("add 5:");
		lfu.put(5, 5);
		lfu.print();
	}
}

应用

redis属于缓存失效的典型应用场景,常见策略如下:

  • noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息( 比较危险)。
  • allkeys-lru:对所有key,优先删除最近最少使用的 key (LRU)。
  • allkeys-random: 对所有key, 随机删除一部分(听起来毫无道理)。
  • volatile-lru:只限于设置了 expire 的key,优先删除最近最少使用的key (LRU)。
  • volatile-random:只限于设置了 expire 的key,随机删除一部分。
  • volatile-ttl:只限于设置了 expire 的key,优先删除剩余时间(TTL) 短的key。
posted @ 2022-02-05 23:39  请务必优秀  阅读(60)  评论(0编辑  收藏  举报