今天主要分享一个LRUmap的实现。我们经常会用到需要使用map来保存数据的时候,由于map本身的映射高效,最适合做随机读取的存储结构。当然LRU算法是在有限大小的存储集合下的一种调度算法,即最近最少使用。对于一个给定大小限制的容器,如何分配资源就涉及到调度,而LRU就是一种经典的调度了,在容器中定义一个最后使用时间,当容器满时,再来新的元素,那么淘汰最近最少使用的元素,把新的元素替换之,这是最直接的思想。
apache common-collections框架里有一个LRUMap的实现,其继承自抽象的linkedmap和抽象的hashmap。下面给出一段测试代码,来看看LRU的直观效果:
1: public static void main(String[] args) {
2: // TODO Auto-generated method stub
3: LRUMap map = new LRUMap(3);
4: map.put("1", 1);
5: map.put("2", 2);
6: //map.get("1");
7: map.put("3", 3);
8: map.put("4", 4);
9:
10: for(Iterator it = map.entrySet().iterator();it.hasNext();){
11: System.out.println(it.next());
12: }
13: }
使用的版本是common-collections3.2.1,直接执行,结果会显示
2=2
3=3
4=4
如果把第6行的注释打开,那么执行结果会是
1=1
3=3
4=4
这样就符合了LRU的原理,在调用了一次get后,1对应的数据不再是最近最少使用。
具体实现也颇有趣,LRUmap继承linkedmap,维护了一个linked list来保存内部数据
1: /** Header in the linked list */
2: protected transient LinkEntry header;
linkentry又是一个循环双向的链表,且继承了hashentry,hashentry虽然也是commons框架自己实现,但是与jdk的实现同理,也是利用链接桶来预防冲突
1: protected static class LinkEntry extends HashEntry {
2: /** The entry before this one in the order */
3: protected LinkEntry before;
4: /** The entry after this one in the order */
5: protected LinkEntry after;
6:
7: /**
8: * Constructs a new entry.
9: *
10: * @param next the next entry in the hash bucket sequence
11: * @param hashCode the hash code
12: * @param key the key
13: * @param value the value
14: */
15: protected LinkEntry(HashEntry next, int hashCode, Object key, Object value) {
16: super(next, hashCode, key, value);
17: }
18: }
当进行put操作时,LRUMap会调用abstracthashmap的put方法,与传统一样,计算hashcode,在对应的hashcode桶定位index,然后做一个addmapping操作。本来在抽象类中的addmapping是传统的,等同于jdk中hashmap的addentry,但是LRUMap这里重写了addmapping,主要进行了map是否已满的判断,如果map未满,那么直接插入,否则,LRU将会定位到将被替换掉的entry的位置,然后做一个reuseMapping的操作,将该替换掉的entryremove,将新加进来的entry放到队尾。这样就完成了put操作。
进行get操作时,首先依据hashmap的原则找到entry,在返回value之前进行了LRU调整moveToMRU操作。该操作判断这个entry是否是队尾,如果是,那么什么都不用干,它就是最近被使用的,如果不是,那么调整它到队尾。
全部的源码见下:
1: /*
2: * Licensed to the Apache Soft