Change Dir

          先知cd——熱愛生活是一切藝術的開始

          統計

          留言簿(18)

          積分與排名

          “牛”們的博客

          各個公司技術

          我的鏈接

          淘寶技術

          閱讀排行榜

          評論排行榜

          分享一個LRUMap的實現——來自apache common-collections框架

          今天主要分享一個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 Software Foundation (ASF) under one or more
             3:  *  contributor license agreements.  See the NOTICE file distributed with
             4:  *  this work for additional information regarding copyright ownership.
             5:  *  The ASF licenses this file to You under the Apache License, Version 2.0
             6:  *  (the "License"); you may not use this file except in compliance with
             7:  *  the License.  You may obtain a copy of the License at
             8:  *
             9:  *      http://www.apache.org/licenses/LICENSE-2.0
            10:  *
            11:  *  Unless required by applicable law or agreed to in writing, software
            12:  *  distributed under the License is distributed on an "AS IS" BASIS,
            13:  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
            14:  *  See the License for the specific language governing permissions and
            15:  *  limitations under the License.
            16:  */
            17: package org.apache.commons.collections.map;
            18:  
            19: import java.io.IOException;
            20: import java.io.ObjectInputStream;
            21: import java.io.ObjectOutputStream;
            22: import java.io.Serializable;
            23: import java.util.Map;
            24:  
            25: import org.apache.commons.collections.BoundedMap;
            26:  
            27: /**
            28:  * A <code>Map</code> implementation with a fixed maximum size which removes
            29:  * the least recently used entry if an entry is added when full.
            30:  * <p>
            31:  * The least recently used algorithm works on the get and put operations only.
            32:  * Iteration of any kind, including setting the value by iteration, does not
            33:  * change the order. Queries such as containsKey and containsValue or access
            34:  * via views also do not change the order.
            35:  * <p>
            36:  * The map implements <code>OrderedMap</code> and entries may be queried using
            37:  * the bidirectional <code>OrderedMapIterator</code>. The order returned is
            38:  * least recently used to most recently used. Iterators from map views can 
            39:  * also be cast to <code>OrderedIterator</code> if required.
            40:  * <p>
            41:  * All the available iterators can be reset back to the start by casting to
            42:  * <code>ResettableIterator</code> and calling <code>reset()</code>.
            43:  * <p>
            44:  * <strong>Note that LRUMap is not synchronized and is not thread-safe.</strong>
            45:  * If you wish to use this map from multiple threads concurrently, you must use
            46:  * appropriate synchronization. The simplest approach is to wrap this map
            47:  * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw 
            48:  * <code>NullPointerException</code>'s when accessed by concurrent threads.
            49:  *
            50:  * @since Commons Collections 3.0 (previously in main package v1.0)
            51:  * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
            52:  *
            53:  * @author James Strachan
            54:  * @author Morgan Delagrange
            55:  * @author Stephen Colebourne
            56:  * @author Mike Pettypiece
            57:  * @author Mario Ivankovits
            58:  */
            59: public class LRUMap
            60:         extends AbstractLinkedMap implements BoundedMap, Serializable, Cloneable {
            61:     
            62:     /** Serialisation version */
            63:     private static final long serialVersionUID = -612114643488955218L;
            64:     /** Default maximum size */
            65:     protected static final int DEFAULT_MAX_SIZE = 100;
            66:     
            67:     /** Maximum size */
            68:     private transient int maxSize;
            69:     /** Scan behaviour */
            70:     private boolean scanUntilRemovable;
            71:  
            72:     /**
            73:      * Constructs a new empty map with a maximum size of 100.
            74:      */
            75:     public LRUMap() {
            76:         this(DEFAULT_MAX_SIZE, DEFAULT_LOAD_FACTOR, false);
            77:     }
            78:  
            79:     /**
            80:      * Constructs a new, empty map with the specified maximum size.
            81:      *
            82:      * @param maxSize  the maximum size of the map
            83:      * @throws IllegalArgumentException if the maximum size is less than one
            84:      */
            85:     public LRUMap(int maxSize) {
            86:         this(maxSize, DEFAULT_LOAD_FACTOR);
            87:     }
            88:  
            89:     /**
            90:      * Constructs a new, empty map with the specified maximum size.
            91:      *
            92:      * @param maxSize  the maximum size of the map
            93:      * @param scanUntilRemovable  scan until a removeable entry is found, default false
            94:      * @throws IllegalArgumentException if the maximum size is less than one
            95:      * @since Commons Collections 3.1
            96:      */
            97:     public LRUMap(int maxSize, boolean scanUntilRemovable) {
            98:         this(maxSize, DEFAULT_LOAD_FACTOR, scanUntilRemovable);
            99:     }
           100:  
           101:     /**
           102:      * Constructs a new, empty map with the specified initial capacity and
           103:      * load factor. 
           104:      *
           105:      * @param maxSize  the maximum size of the map, -1 for no limit,
           106:      * @param loadFactor  the load factor
           107:      * @throws IllegalArgumentException if the maximum size is less than one
           108:      * @throws IllegalArgumentException if the load factor is less than zero
           109:      */
           110:     public LRUMap(int maxSize, float loadFactor) {
           111:         this(maxSize, loadFactor, false);
           112:     }
           113:  
           114:     /**
           115:      * Constructs a new, empty map with the specified initial capacity and
           116:      * load factor.
           117:      *
           118:      * @param maxSize  the maximum size of the map, -1 for no limit,
           119:      * @param loadFactor  the load factor
           120:      * @param scanUntilRemovable  scan until a removeable entry is found, default false
           121:      * @throws IllegalArgumentException if the maximum size is less than one
           122:      * @throws IllegalArgumentException if the load factor is less than zero
           123:      * @since Commons Collections 3.1
           124:      */
           125:     public LRUMap(int maxSize, float loadFactor, boolean scanUntilRemovable) {
           126:         super((maxSize < 1 ? DEFAULT_CAPACITY : maxSize), loadFactor);
           127:         if (maxSize < 1) {
           128:             throw new IllegalArgumentException("LRUMap max size must be greater than 0");
           129:         }
           130:         this.maxSize = maxSize;
           131:         this.scanUntilRemovable = scanUntilRemovable;
           132:     }
           133:  
           134:     /**
           135:      * Constructor copying elements from another map.
           136:      * <p>
           137:      * The maximum size is set from the map's size.
           138:      *
           139:      * @param map  the map to copy
           140:      * @throws NullPointerException if the map is null
           141:      * @throws IllegalArgumentException if the map is empty
           142:      */
           143:     public LRUMap(Map map) {
           144:         this(map, false);
           145:     }
           146:  
           147:     /**
           148:      * Constructor copying elements from another map.
           149:      * <p/>
           150:      * The maximum size is set from the map's size.
           151:      *
           152:      * @param map  the map to copy
           153:      * @param scanUntilRemovable  scan until a removeable entry is found, default false
           154:      * @throws NullPointerException if the map is null
           155:      * @throws IllegalArgumentException if the map is empty
           156:      * @since Commons Collections 3.1
           157:      */
           158:     public LRUMap(Map map, boolean scanUntilRemovable) {
           159:         this(map.size(), DEFAULT_LOAD_FACTOR, scanUntilRemovable);
           160:         putAll(map);
           161:     }
           162:  
           163:     //-----------------------------------------------------------------------
           164:     /**
           165:      * Gets the value mapped to the key specified.
           166:      * <p>
           167:      * This operation changes the position of the key in the map to the
           168:      * most recently used position (first).
           169:      * 
           170:      * @param key  the key
           171:      * @return the mapped value, null if no match
           172:      */
           173:     public Object get(Object key) {
           174:         LinkEntry entry = (LinkEntry) getEntry(key);
           175:         if (entry == null) {
           176:             return null;
           177:         }
           178:         moveToMRU(entry);
           179:         return entry.getValue();
           180:     }
           181:  
           182:     //-----------------------------------------------------------------------
           183:     /**
           184:      * Moves an entry to the MRU position at the end of the list.
           185:      * <p>
           186:      * This implementation moves the updated entry to the end of the list.
           187:      * 
           188:      * @param entry  the entry to update
           189:      */
           190:     protected void moveToMRU(LinkEntry entry) {
           191:         if (entry.after != header) {
           192:             modCount++;
           193:             // remove
           194:             entry.before.after = entry.after;
           195:             entry.after.before = entry.before;
           196:             // add first
           197:             entry.after = header;
           198:             entry.before = header.before;
           199:             header.before.after = entry;
           200:             header.before = entry;
           201:         } else if (entry == header) {
           202:             throw new IllegalStateException("Can't move header to MRU" +
           203:                 " (please report this to commons-dev@jakarta.apache.org)");
           204:         }
           205:     }
           206:     
           207:     /**
           208:      * Updates an existing key-value mapping.
           209:      * <p>
           210:      * This implementation moves the updated entry to the top of the list
           211:      * using {@link #moveToMRU(AbstractLinkedMap.LinkEntry)}.
           212:      * 
           213:      * @param entry  the entry to update
           214:      * @param newValue  the new value to store
           215:      */
           216:     protected void updateEntry(HashEntry entry, Object newValue) {
           217:         moveToMRU((LinkEntry) entry);  // handles modCount
           218:         entry.setValue(newValue);
           219:     }
           220:     
           221:     /**
           222:      * Adds a new key-value mapping into this map.
           223:      * <p>
           224:      * This implementation checks the LRU size and determines whether to
           225:      * discard an entry or not using {@link #removeLRU(AbstractLinkedMap.LinkEntry)}.
           226:      * <p>
           227:      * From Commons Collections 3.1 this method uses {@link #isFull()} rather
           228:      * than accessing <code>size</code> and <code>maxSize</code> directly.
           229:      * It also handles the scanUntilRemovable functionality.
           230:      * 
           231:      * @param hashIndex  the index into the data array to store at
           232:      * @param hashCode  the hash code of the key to add
           233:      * @param key  the key to add
           234:      * @param value  the value to add
           235:      */
           236:     protected void addMapping(int hashIndex, int hashCode, Object key, Object value) {
           237:         if (isFull()) {
           238:             LinkEntry reuse = header.after;
           239:             boolean removeLRUEntry = false;
           240:             if (scanUntilRemovable) {
           241:                 while (reuse != header && reuse != null) {
           242:                     if (removeLRU(reuse)) {
           243:                         removeLRUEntry = true;
           244:                         break;
           245:                     }
           246:                     reuse = reuse.after;
           247:                 }
           248:                 if (reuse == null) {
           249:                     throw new IllegalStateException(
           250:                         "Entry.after=null, header.after" + header.after + " header.before" + header.before +
           251:                         " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize +
           252:                         " Please check that your keys are immutable, and that you have used synchronization properly." +
           253:                         " If so, then please report this to commons-dev@jakarta.apache.org as a bug.");
           254:                 }
           255:             } else {
           256:                 removeLRUEntry = removeLRU(reuse);
           257:             }
           258:             
           259:             if (removeLRUEntry) {
           260:                 if (reuse == null) {
           261:                     throw new IllegalStateException(
           262:                         "reuse=null, header.after=" + header.after + " header.before" + header.before +
           263:                         " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize +
           264:                         " Please check that your keys are immutable, and that you have used synchronization properly." +
           265:                         " If so, then please report this to commons-dev@jakarta.apache.org as a bug.");
           266:                 }
           267:                 reuseMapping(reuse, hashIndex, hashCode, key, value);
           268:             } else {
           269:                 super.addMapping(hashIndex, hashCode, key, value);
           270:             }
           271:         } else {
           272:             super.addMapping(hashIndex, hashCode, key, value);
           273:         }
           274:     }
           275:     
           276:     /**
           277:      * Reuses an entry by removing it and moving it to a new place in the map.
           278:      * <p>
           279:      * This method uses {@link #removeEntry}, {@link #reuseEntry} and {@link #addEntry}.
           280:      * 
           281:      * @param entry  the entry to reuse
           282:      * @param hashIndex  the index into the data array to store at
           283:      * @param hashCode  the hash code of the key to add
           284:      * @param key  the key to add
           285:      * @param value  the value to add
           286:      */
           287:     protected void reuseMapping(LinkEntry entry, int hashIndex, int hashCode, Object key, Object value) {
           288:         // find the entry before the entry specified in the hash table
           289:         // remember that the parameters (except the first) refer to the new entry,
           290:         // not the old one
           291:         try {
           292:             int removeIndex = hashIndex(entry.hashCode, data.length);
           293:             HashEntry[] tmp = data;  // may protect against some sync issues
           294:             HashEntry loop = tmp[removeIndex];
           295:             HashEntry previous = null;
           296:             while (loop != entry && loop != null) {
           297:                 previous = loop;
           298:                 loop = loop.next;
           299:             }
           300:             if (loop == null) {
           301:                 throw new IllegalStateException(
           302:                     "Entry.next=null, data[removeIndex]=" + data[removeIndex] + " previous=" + previous +
           303:                     " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize +
           304:                     " Please check that your keys are immutable, and that you have used synchronization properly." +
           305:                     " If so, then please report this to commons-dev@jakarta.apache.org as a bug.");
           306:             }
           307:             
           308:             // reuse the entry
           309:             modCount++;
           310:             removeEntry(entry, removeIndex, previous);
           311:             reuseEntry(entry, hashIndex, hashCode, key, value);
           312:             addEntry(entry, hashIndex);
           313:         } catch (NullPointerException ex) {
           314:             throw new IllegalStateException(
           315:                     "NPE, entry=" + entry + " entryIsHeader=" + (entry==header) +
           316:                     " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize +
           317:                     " Please check that your keys are immutable, and that you have used synchronization properly." +
           318:                     " If so, then please report this to commons-dev@jakarta.apache.org as a bug.");
           319:         }
           320:     }
           321:     
           322:     /**
           323:      * Subclass method to control removal of the least recently used entry from the map.
           324:      * <p>
           325:      * This method exists for subclasses to override. A subclass may wish to
           326:      * provide cleanup of resources when an entry is removed. For example:
           327:      * <pre>
           328:      * protected boolean removeLRU(LinkEntry entry) {
           329:      *   releaseResources(entry.getValue());  // release resources held by entry
           330:      *   return true;  // actually delete entry
           331:      * }
           332:      * </pre>
           333:      * <p>
           334:      * Alternatively, a subclass may choose to not remove the entry or selectively
           335:      * keep certain LRU entries. For example:
           336:      * <pre>
           337:      * protected boolean removeLRU(LinkEntry entry) {
           338:      *   if (entry.getKey().toString().startsWith("System.")) {
           339:      *     return false;  // entry not removed from LRUMap
           340:      *   } else {
           341:      *     return true;  // actually delete entry
           342:      *   }
           343:      * }
           344:      * </pre>
           345:      * The effect of returning false is dependent on the scanUntilRemovable flag.
           346:      * If the flag is true, the next LRU entry will be passed to this method and so on
           347:      * until one returns false and is removed, or every entry in the map has been passed.
           348:      * If the scanUntilRemovable flag is false, the map will exceed the maximum size.
           349:      * <p>
           350:      * NOTE: Commons Collections 3.0 passed the wrong entry to this method.
           351:      * This is fixed in version 3.1 onwards.
           352:      * 
           353:      * @param entry  the entry to be removed
           354:      */
           355:     protected boolean removeLRU(LinkEntry entry) {
           356:         return true;
           357:     }
           358:  
           359:     //-----------------------------------------------------------------------
           360:     /**
           361:      * Returns true if this map is full and no new mappings can be added.
           362:      *
           363:      * @return <code>true</code> if the map is full
           364:      */
           365:     public boolean isFull() {
           366:         return (size >= maxSize);
           367:     }
           368:  
           369:     /**
           370:      * Gets the maximum size of the map (the bound).
           371:      *
           372:      * @return the maximum number of elements the map can hold
           373:      */
           374:     public int maxSize() {
           375:         return maxSize;
           376:     }
           377:  
           378:     /**
           379:      * Whether this LRUMap will scan until a removable entry is found when the
           380:      * map is full.
           381:      *
           382:      * @return true if this map scans
           383:      * @since Commons Collections 3.1
           384:      */
           385:     public boolean isScanUntilRemovable() {
           386:         return scanUntilRemovable;
           387:     }
           388:  
           389:     //-----------------------------------------------------------------------
           390:     /**
           391:      * Clones the map without cloning the keys or values.
           392:      *
           393:      * @return a shallow clone
           394:      */
           395:     public Object clone() {
           396:         return super.clone();
           397:     }
           398:     
           399:     /**
           400:      * Write the map out using a custom routine.
           401:      */
           402:     private void writeObject(ObjectOutputStream out) throws IOException {
           403:         out.defaultWriteObject();
           404:         doWriteObject(out);
           405:     }
           406:  
           407:     /**
           408:      * Read the map in using a custom routine.
           409:      */
           410:     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
           411:         in.defaultReadObject();
           412:         doReadObject(in);
           413:     }
           414:     
           415:     /**
           416:      * Writes the data necessary for <code>put()</code> to work in deserialization.
           417:      */
           418:     protected void doWriteObject(ObjectOutputStream out) throws IOException {
           419:         out.writeInt(maxSize);
           420:         super.doWriteObject(out);
           421:     }
           422:  
           423:     /**
           424:      * Reads the data necessary for <code>put()</code> to work in the superclass.
           425:      */
           426:     protected void doReadObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
           427:         maxSize = in.readInt();
           428:         super.doReadObject(in);
           429:     }
           430:     
           431: }

          部分操作需要追溯其父類代碼,這里就不貼了,有興趣的朋友可以直接到源碼閱讀。

          LRUMap對于構建緩存或者連接池之類的技術經常用到,common-collections框架給了現成的實現,大家在不需要修改的情況下,完全可以不必reinvent the wheel,直接用,好用且穩定。

          posted on 2012-05-31 13:16 changedi 閱讀(2663) 評論(1)  編輯  收藏 所屬分類: Java技術好代碼分享

          評論

          # re: 分享一個LRUMap的實現&mdash;&mdash;來自apache common-collections框架 2012-06-02 10:53 工業輔料

          網頁設計也涉及到框架的哈  回復  更多評論   

          主站蜘蛛池模板: 五台县| 南溪县| 陕西省| 承德市| 龙泉市| 昂仁县| 靖边县| 北京市| 鄢陵县| 宜黄县| 介休市| 霸州市| 云南省| 府谷县| 垦利县| 四川省| 庐江县| 永登县| 凌海市| 垫江县| 东阳市| 中江县| 洪泽县| 万州区| 莒南县| 镇坪县| 长治县| 宜黄县| 青河县| 时尚| 资讯 | 万年县| 高淳县| 四川省| 云浮市| 汉寿县| 武隆县| 天长市| 凌源市| 永昌县| 望江县|