带你学LRU算法
前言
名字听起来挺高大上的,其实内部的原理非常简单,英文全称是Least Recently Used,即最近最少使用算法。
正文
这算法最初还是源自Linux的内存管理。核心的思想就是,当出现资源不够用的时候,可以把那些使用频率低的资源给释放掉,然后拿来用!这样看起来还是很凶残的,那我举一格例子,是小灰的漫画算法中提到的。
用户在访问数据库的时候,程序员会为此做一些缓存记录,以便下一次用户再次访问同样的数据时可以避免磁盘操作,听起来还是很不错的对吧。
可是这个缓存咱们不能无限整下去,不然内存都不够你用的。
核心
将key-value数据对,或者是其他方式的资源,按照时间顺序,进行排序。然后用双向链表将这些数据对,统一起来。当一个资源被重新访问时,就将其放到链表的头部(相当于越是靠近链表头部的就越新),直到某次插入面临资源不足的情况了,就将链表的最后一个删除掉。
代码实现
实在是太简单了,咱们就不浪费时间了好吧。我去网上随便偷一下别人的代码实现这事儿就算过去了。
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
这应该才是我想看的东西,上面那些就很入门。咱们先考虑一下线程安全该如何实现。咱们首先得明确一个概念,就是LRU究竟是双链表还是哈希表。我认为,LRU本质上还是哈希表,只是哈希的value是带有双链表的东西。所以线程安全,本质上说的是哈希表的线程安全。(上面那份代码我找的不合适,感觉那是人家语言封装好的东西)
所以给哈希表上个锁就很棒了。
那么高吞吐呢?
很明显,不管是读操作还是写操作,最后都要修改这个哈希表,这是制约吞吐的关键因素。