前言
LRU是Least Recently Used ,最近最少使用算法,第一次见到这个名词是在学操作系统、后面学网络原理时也遇到过一次。这一次在代码里看到大佬了用了这个实现缓存,研究了一下~ 一开始还挺纳闷,为啥不直接用先用现有的工具如redis去作缓存处理,大佬给出的解释是,用这个实现缓存是在JVM中实现的,效率会比redis快很多,特别数据量多的时候(他那边是有这种情况),根据压测的效果来看,当数据量达到10w时,可以比redis快100倍左右。不过,话又说回来了,这种缓存也有相应的弊端,持久化是个问题?JVM也需要作相应的调优,缓存算法也需要限制capacity,不然JVM压力过大,可能会造成内存溢出。
原理
该算法基于LinkedHashMap,链表加上一个哈希,链表对元素增删的方便性,加上哈希读取元素的快速性,使得整个操作的时间复杂度大大降低。
其中,链表是一个双向链表,LinkedHashMap 内部维护一个双向队列,在初始化时也会给定一个缓存大小的阈值capacity。初始化时自定义是否需要删除最近不常使用的数据,如果是则会按照我们重写的方式进行实现(就是重写LinkedHashMap中的removeEldestEntry方法)。
LinkedHashMap可以根据我们放入map的元素的顺序进行打印,当我们访问其中某个元素时,它会将访问过的元素放置于底端,而头部则是比较少访问的元素了~当元素超过规定的容量时,LinkedHashMap会优先删除头部元素,则为LRU的实现提供了捷径。
实现
通过重入锁实现多线程的版本,确保数据的安全性~
public class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> {
private final int maxCapacity;
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
private final Lock lock = new ReentrantLock();
public LRULinkedHashMap(int maxCapacity) {
super(maxCapacity, DEFAULT_LOAD_FACTOR, true);
this.maxCapacity = maxCapacity;
}
/**
* 实现LRU的关键方法,如果map里面的元素个数大于了缓存最大容量,则删除链表的顶端元素
* 初始化时自定义是否需要删除最近不常使用的数据,如果是则会按照实现的方式管理数据。
* @param eldest
* @return
*/
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > maxCapacity;
}
@Override
public boolean containsKey(Object key) {
try {
lock.lock();
return super.containsKey(key);
} finally {
lock.unlock();
}
}
@Override
public V get(Object key) {
try {
lock.lock();
return super.get(key);
} finally {
lock.unlock();
}
}
@Override
public V put(K key, V value) {
try {
lock.lock();
return super.put(key, value);
} finally {
lock.unlock();
}
}
public int size() {
try {
lock.lock();
return super.size();
} finally {
lock.unlock();
}
}
public void clear() {
try {
lock.lock();
super.clear();
} finally {
lock.unlock();
}
}
public Collection<Map.Entry<K, V>> getAll() {
try {
lock.lock();
return new ArrayList<>(super.entrySet());
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
LRULinkedHashMap<Integer,Integer> test = new LRULinkedHashMap<>(4);
test.put(0,0);
test.put(1,1);
test.put(2,2);
test.put(3,3);
//访问到的元素移动到了底部
System.out.println("访问第一个元素:="+test.get(0));
test.put(4,4);
Iterator it = test.entrySet().iterator();
while (it.hasNext()){
System.out.println(it.next());
}
}
}