LinkedHashMap实现LRU以及其在spring-mvc中AbstractCachingViewResolver运用
一、LinkedHashMap实现LRU
1、LinkedHashMap有一个三个参数的构造函数,new LinkedHashMap<Object, View>(size, 0.75f, true),其中第三个参数accessOrder的作用是如果元素被访问的情况下,是否把元素添加到链表的尾部。结合LinkedHashMap的一个protected的removeEldestEntry方法,可以实现LRU(即最久没访问的移除)。
LinkedHashMap<Object,Object> map = new LinkedHashMap<Object, Object>(100, 0.75f, true);
map.put("1", "a");
map.put("2", "b");
map.put("3", "c");
map.put("4", "d");
for (Iterator<Object> iterator = map.values().iterator(); iterator.hasNext();) {
String name = (String) iterator.next();
System.out.print(name); // 默认是abcd
}
System.out.println("........................................................");
map.get("1");
map.get("2");
System.out.println(map);
for (Iterator<Object> iterator = map.values().iterator(); iterator.hasNext();) {
String name = (String) iterator.next();
System.out.print(name); // 变成了cdab 即被访问后,元素的顺序被放到了最后面。
}
从上面我们可以看到,元素添加abcd,如果没有使用下面的get方法,那么遍历的时候,结果也是abcd,但是get元素后,元素被移动到尾部,即等于这个元素移动到最新添加的位置,那么最后的结果就变成了cdab。
2、LinkedHashMap如何实现LRU,LRU是缓存淘汰策略里的一种,即最久没有使用进行移除,上面说的removeEldestEntry方法,如果返回ture,并且当容器的size大于初始给定的size时,那么就会移除掉链表的最前面的元素。看下面的案例。
// 如果对LinkedHashMap的removeEldestEntry方法进行复写,即大于容量,进行移除最先插入的。
LinkedHashMap<Object,Object> map2 = new LinkedHashMap<Object, Object>(20, 0.75f, true){
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
if (size() > 20) {
return true;
}else {
return false;
}
}
};
for (int i = 0; i < 30; i++) {
map2.put(i+"", i);
if(i>15) {
map2.get(i+"");
}
}
// 因为最后添加的15个元素持续被添加到map的结尾,那么最先添加10个元素就会被移除。
System.out.println(map2);
二、AbstractCachingViewResolver中的运用
AbstractCachingViewResolver是用来创建View的抽象基类,其创建功能是由子类模板方法实现的,它的主要功能就是缓存创建的View,但是为了不会无限的创建View,占用内存空间,这个类就利用LinkedHashMap的三个构造函数,在缓存1000个view后,会自动移除最久没访问的View对象。
细节实现是其内部有二个map变量,其中一个ConcurrentHashMap的实现用来快速读取view,如果我们无限缓存,就不必要引入另外一个LinkedHashMap了,另外一个map的作用就是同步创建更新删除View,并且LinkedHashMap加锁更新的时候也对ConcurrentHashMap进行了更新,这样我们就达到了利用一个非线程安全的LinkedHashMap利用加锁操作实现LRU的同时,对只提供访问的ConcurrentHashMap也进行了LRU的移除操作。
代码演示:
/**
* MyCacheViewResolve的演示
* 创建30个view,当容量大于限制20个时,会进行移除,而viewCreationCache只有put操作访问,没有获取操作
* 即是移除最先添加的,最终结果就是10-29的view存在。
*/
public static void main(String[] args) throws Exception {
MyCacheViewResolve resolve = new MyCacheViewResolve();
for (int i = 0; i < 30; i++) {
resolve.resolveViewName("view"+i);
}
System.out.println(resolve); // 10-29存在。
}
}
class MyCacheViewResolve {
public static final int DEFAULT_CACHE_LIMIT = 20;
private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;
private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<Object, View>(DEFAULT_CACHE_LIMIT);
@SuppressWarnings("serial")
private final Map<Object, View> viewCreationCache = new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) {
if (size() > cacheLimit) { // 大于cachelimit时,reutrn true,能删除自身的,并且额外移除viewAccessCache的
viewAccessCache.remove(eldest.getKey());
return true;
} else {
return false;
}
}
};
@Override
public String toString() {
return "MyCacheViewResolve [viewAccessCache=" + viewCreationCache + "]";
}
public View resolveViewName(String viewName) throws Exception {
View view = this.viewAccessCache.get(viewName); // 从concurrentMap缓存中取
if (view == null) {
synchronized (this.viewCreationCache) { // 空就同步双检CreateMap创建
view = this.viewCreationCache.get(viewName); //
if (view == null) {
view = createView(viewName);
if (view != null) {
this.viewAccessCache.put(viewName, view);
this.viewCreationCache.put(viewName, view);
}
}
}
}
return view;
}
private View createView(String viewName) {
return new View(viewName);
}
public void removeFromCache(String viewName) {
synchronized (this.viewCreationCache) {
this.viewAccessCache.remove(viewName);
this.viewCreationCache.remove(viewName);
}
}
}
class View {
private String name;
public View(String name) {
this.name = name;
}
@Override
public String toString() {
return "View [name=" + name + "]";
}
}