这是我参与8月更文挑战的第27天,活动详情查看: 8月更文挑战
Redis是我们比较常用的缓存系统,因为缓存是存放在内存中,内存的容量有限,不能像磁盘一样可以装几个T的容量,当我们不停的往缓存中存放数据,那么到一定的时候就会将缓存称爆,这个时候就需要淘汰算法来计算出哪些缓存数据需要删除,然后将数据从内存中清理,腾出空间给其他数据使用。比较常用的淘汰算法有先来先淘汰(FIFO)、最久未使用淘汰(LRU)、最久最少使用(LFU)这三大类算法。
先来先淘汰(FIFO)
- 概述
这种算法就是字面的意思:先来的先被淘汰(First In First Out),往队列插入数据的时候,发现队列已满,就把最早插入的数据淘汰掉,然后插入新的数据
- 算法实现介绍
在这里我们可以采用LinkedList来实现,每次往头部添加数据,然后判断如果容量大于已有的容量,就将队尾的数据取出淘汰掉,大致实现原理如下面的图:
这种算法实现起来比较简单,内部维护一个有序的链表就行,但是有它的一定缺点:就是这个只管数据在最后一个都会被淘汰掉,如果被淘汰的数据一直频繁使用,每次被淘汰掉后,都要去其他地方把数据查询回来然后扔到缓存中,这样会造成额外的开销。
- 简易代码
public class FIFO {
LinkedList<Integer> fifo = new LinkedList<Integer>();
int MAX_SIZE = 5;
public void add(int item) {
fifo.addFirst(item);
if (fifo.size() > MAX_SIZE) {
fifo.removeLast();
}
}
public void read(int item) {
Iterator<Integer> iterator = fifo.iterator();
while (iterator.hasNext()) {
int j = iterator.next();
if (item == j) {
System.out.println("find it!");
return;
}
}
System.out.println("not found!");
}
}
最久未使用淘汰(LRU)
- 概述
字面意思就是淘汰最长时间没有使用过的数据(Least Recently Used),相比先来先淘汰的算法没有那么粗暴,LRU算法认为过去频繁使用的数据,在未来的时候可能会被使用掉,如果被淘汰的话,会增加额外的开销,所以就优先淘汰那些很久为被使用的元素,Redis的淘汰机制就有LRU算法。
- 算法实现介绍
实现的数据结构有很多种,这里仍然采用LinkedList来实现,数据的加入也是从头部增加,淘汰的时候从尾部取出来淘汰,但是相对于FIFO算法而已,在使用的时候做一点改进,每次使用到元素后,将命中的缓存数据从原来的位置摘出来,然后将元素重新插入到头部中,如何一个元素被频繁使用,那么它就会不停的被取来插入到头部中,这样的话就不会被淘汰。
- 简易代码
public class LRU {
LinkedList<Integer> lru = new LinkedList<Integer>();
int size = 3;
public void add(int i) {
lru.addFirst(i);
if (lru.size() > size) {
lru.removeLast();
}
}
public 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);
return;
}
index++;
}
System.out.println("not found!");
}
}
最久最少使用(LFU)
- 概述
最近最少使用的元素被淘汰(Least Freuently Used),淘汰最近一段时间内,使用次数最少的使用,相比LRU算法,在淘汰的时候增加了一层判断,不只是根据最久未使用的来淘汰,增加了最少使用这个维度指标来一同判断该元素是否能被淘汰。
- 算法实现介绍
这种算法实现的时候,需要在每个元素上面内置一个计数器和访问时间,这样的话当出现同一段时间内,访问次数相同的情况,就可以根据访问时间来判断哪个元素被淘汰。在实现次数相同,根据访问时间比较的时候,我们可以自定义一个实体对象,继承Comparable接口,然后重写compareTo方法,这样话,在对集合排序的时候,可以根据我们重写的compareTo方法来比较顺序,可以获取到最小的一个元素,然后淘汰掉。
- 简易代码
public class LFU {
private final int size = 5;
private Map<Integer, Integer> cache = new HashMap<>();
private Map<Integer, Dto> count = new HashMap<>();
public 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;
}
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());
}
class Dto implements Comparable<Dto> {
private Integer key;
private int count;
private long lastTime;
@Override
public int compareTo(Dto o) {
int compare = Integer.compare(this.count, o.count);
return compare == 0 ? Long.compare(this.lastTime, o.lastTime) : compare;
}
}
}
上面三种淘汰机制在实际中用的比较多,主要淘汰算法用于缓存服务中,用来淘汰我们存入的缓存数据。