JAVA的LRU缓存介绍与实现引子:我们平时总会有一个电话本记录所有朋友的电话,但是,如果有朋友经常联系,那些朋友的电话号码不用翻电话本我们 也能记住,但是,如果长时间没有联系了,要再次联系那位朋友的时候,我们又不得不求助电话本,但是,通过电话本查找还是很费时间的。但是, 我们大脑能够记住的东西是一定的,我们只能记住自己最熟悉的,而长时间不熟悉的自然就忘记了。其实,计算机也用到了同样的一个概念,我们用 缓存来存放以前读取的数据,而不是直接丢掉,这样,再次读取的时候,可以直接在缓存里面取,而不用再重新查找一遍,这样系统的反应能力会有 很大提高。但是,当我们读取的个数特别大的时候,我们不可能把所有已经读取的数据都放在缓存里,毕竟内存大小是一定的,我们一般把最近常读 取的放在缓存里(相当于我们把最近联系的朋友的姓名和电话放在大脑里一样)。现在,我们就来研究这样一种缓存机制。LRU缓存:LRU缓存 利用了这样的一种思想。LRU是LeastRecentlyUsed的缩写,翻译过来就是“最近最少使用”,也就是说,LRU缓存把 最近最少使用的数据移除,让给最新读取的数据。而往往最常读取的,也是读取次数最多的,所以,利用LRU缓存,我们能够提高系统的perf ormance.实现:要实现LRU缓存,我们首先要用到一个类LinkedHashMap。用这个类有两大好处:一是它本身已经实现 了按照访问顺序的存储,也就是说,最近读取的会放在最前面,最最不常读取的会放在最后(当然,它也可以实现按照插入顺序存储)。第二,Li nkedHashMap本身有一个方法用于判断是否需要移除最不常读取的数,但是,原始方法默认不需要移除(这是,LinkedHashM ap相当于一个linkedlist),所以,我们需要override这样一个方法,使得当缓存里存放的数据个数超过规定个数后,就把最 不常用的移除掉。LinkedHashMap的API写得很清楚,推荐大家可以先读一下。要基于LinkedHashMap来实现LRU缓 存,我们可以选择inheritance,也可以选择delegation,我更喜欢delegation。基于delegatio n的实现已经有人写出来了,而且写得很漂亮,我就不班门弄斧了。代码如下:importjava.util.LinkedHashMap ;importjava.util.Collection;importjava.util.Map;importjava.uti l.ArrayList;/AnLRUcache,basedonLinkedHashMap >.Thiscachehasafixedmaximumnumberofelements(e>cacheSize).Ifthecacheisfullandanotherentryisa dded,theLRU(leastrecentlyused)entryisdropped.This classisthread-safe.Allmethodsofthisclassaresynchronized . Author:Christiand''Heureuse,InventecInformatikAG,Zu rich,Switzerland Multi-licensed:EPL/LGPL/GPL/AL/BS D./publicclassLRUCache{privatestaticfinalfloathashTab leLoadFactor=0.75f;privateLinkedHashMapmap;privateintc acheSize;/CreatesanewLRUcache.@paramcacheSizethemaxi mumnumberofentriesthatwillbekeptinthiscache./publicLR UCache(intcacheSize){this.cacheSize=cacheSize;inthashTabl eCapacity=(int)Math.ceil(cacheSize/hashTableLoadFactor)+1; map=newLinkedHashMap(hashTableCapacity,hashTableLoadFact or,true){//(ananonymousinnerclass)privatestaticfinallo ngserialVersionUID=1;@OverrideprotectedbooleanremoveEldest Entry(Map.Entryeldest){returnsize()>LRUCache.this.cac heSize;}};}/Retrievesanentryfromthecache. Theret rievedentrybecomestheMRU(mostrecentlyused)entry.@param keythekeywhoseassociatedvalueistobereturned.@return thevalueassociatedtothiskey,ornullifnovaluewiththis keyexistsinthecache./publicsynchronizedVget(Kkey){ret urnmap.get(key);}/Addsanentrytothiscache.Thenewent rybecomestheMRU(mostrecentlyused)entry.Ifanentrywith thespecifiedkeyalreadyexistsinthecache,itisreplacedby thenewentry.Ifthecacheisfull,theLRU(leastrecentlyuse d)entryisremovedfromthecache.@paramkeythekeywithw hichthespecifiedvalueistobeassociated.@paramvalueava luetobeassociatedwiththespecifiedkey./publicsynchronized voidput(Kkey,Vvalue){map.put(key,value);}/Clearst hecache./publicsynchronizedvoidclear(){map.clear();}/ Returnsthenumberofusedentriesinthecache.@returnthenum berofentriescurrentlyinthecache./publicsynchronizedintu sedEntries(){returnmap.size();}/ReturnsaCollectio n thatcontainsacopyofallcacheentries.@returnaode>Collectionwithacopyofthecachecontent./publics ynchronizedCollection>getAll(){returnnewArra yList>(map.entrySet());}}//endclassLRUCache-- ----------------------------------------------------------------- -----------------------//TestroutinefortheLRUCacheclass.pub licstaticvoidmain(String[]args){LRUCachec =newLRUCache(3);c.put("1","one"); //1c.put("2","two"); //21c.put("3","three");//321c .put("4","four");//432if(c.get(" 2")==null)thrownewError();//243c.put("5","five"); //524c.put("4","secondfour"); //452//Verifycachecontent.if(c.usedEntrie s()!=3)thrownewError();if(!c.get("4").equals( "secondfour"))thrownewError();if(!c.get("5").equals("five") )thrownewError();if(!c.get("2").equals("two")) thrownewError();//Listcachecontent.for(Map.Entry,String>e:c.getAll())System.out.println(e.getKey()+":" +e.getValue());}代码出自:http://www.source-code.biz/snippets/java/6 .htm在博客http://gogole.iteye.com/blog/692103http://gogole.iteye.co m/blog/692103里,作者使用的是双链表+hashtable的方式实现的。如果在面试题里考到如何实现LRU,考官一 般会要求使用双链表+hashtable的方式。所以,我把原文的部分内容摘抄如下:双链表+hashtable实现原理:将 Cache的所有位置都用双连表连接起来,当一个位置被命中之后,就将通过调整链表的指向,将该位置调整到链表头的位置,新加入的Cach e直接加到链表头中。这样,在多次进行Cache操作后,最近被命中的,就会被向链表头方向移动,而没有命中的,而想链表后面移动,链表尾 则表示最近最少使用的Cache。当需要替换内容时候,链表的最后位置就是最少被命中的位置,我们只需要淘汰链表最后的部分即可。publ icclassLRUCache{privateintcacheSize;privateHashtable |