一个LRUMap的实现——来自apache common-collections框架
2016-03-04 23:34
661 查看
30. 下面哪个Map最适合用来实现LRU Cache?
A. Hashtable
B. TreeMap
C. HashMap
D. IdentityHashMap
E. WeakHashMap
网上给的答案是A。为什么?
大家一般如何实现这个 最近最少使用 cache?
今天主要分享一个LRUmap的实现。我们经常会用到需要使用map来保存数据的时候,由于map本身的映射高效,最适合做随机读取的存储结构。当然LRU算法是在有限大小的存储集合下的一种调度算法,即最近最少使用。对于一个给定大小限制的容器,如何分配资源就涉及到调度,而LRU就是一种经典的调度了,在容器中定义一个最后使用时间,当容器满时,再来新的元素,那么淘汰最近最少使用的元素,把新的元素替换之,这是最直接的思想。
apache common-collections框架里有一个LRUMap的实现,其继承自抽象的linkedmap和抽象的hashmap。下面给出一段测试代码,来看看LRU的直观效果:
使用的版本是common-collections3.2.1,直接执行,结果会显示
2=2
3=3
4=4
如果把第6行的注释打开,那么执行结果会是
1=1
3=3
4=4
这样就符合了LRU的原理,在调用了一次get后,1对应的数据不再是最近最少使用。
具体实现也颇有趣,LRUmap继承linkedmap,维护了一个linked list来保存内部数据
linkentry又是一个循环双向的链表,且继承了hashentry,hashentry虽然也是commons框架自己实现,但是与jdk的实现同理,也是利用链接桶来预防冲突
当进行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是否是队尾,如果是,那么什么都不用干,它就是最近被使用的,如果不是,那么调整它到队尾。
全部的源码见下:
A. Hashtable
B. TreeMap
C. HashMap
D. IdentityHashMap
E. WeakHashMap
网上给的答案是A。为什么?
大家一般如何实现这个 最近最少使用 cache?
今天主要分享一个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[/code]10: *11: * Unless required by applicable law or agreed to in writing, software12: * 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 and15: * 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 removes29: * 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 not33: * change the order. Queries such as containsKey and containsValue or access34: * via views also do not change the order.35: * <p>36: * The map implements <code>OrderedMap</code> and entries may be queried using37: * the bidirectional <code>OrderedMapIterator</code>. The order returned is38: * least recently used to most recently used. Iterators from map views can39: * 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 to42: * <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 use46: * appropriate synchronization. The simplest approach is to wrap this map47: * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw48: * <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 Strachan54: * @author Morgan Delagrange55: * @author Stephen Colebourne56: * @author Mike Pettypiece57: * @author Mario Ivankovits58: */59: public class LRUMap60: 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 map83: * @throws IllegalArgumentException if the maximum size is less than one84: */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 map93: * @param scanUntilRemovable scan until a removeable entry is found, default false94: * @throws IllegalArgumentException if the maximum size is less than one95: * @since Commons Collections 3.196: */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 and103: * load factor.104: *105: * @param maxSize the maximum size of the map, -1 for no limit,106: * @param loadFactor the load factor107: * @throws IllegalArgumentException if the maximum size is less than one108: * @throws IllegalArgumentException if the load factor is less than zero109: */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 and116: * load factor.117: *118: * @param maxSize the maximum size of the map, -1 for no limit,119: * @param loadFactor the load factor120: * @param scanUntilRemovable scan until a removeable entry is found, default false121: * @throws IllegalArgumentException if the maximum size is less than one122: * @throws IllegalArgumentException if the load factor is less than zero123: * @since Commons Collections 3.1124: */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 copy140: * @throws NullPointerException if the map is null141: * @throws IllegalArgumentException if the map is empty142: */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 copy153: * @param scanUntilRemovable scan until a removeable entry is found, default false154: * @throws NullPointerException if the map is null155: * @throws IllegalArgumentException if the map is empty156: * @since Commons Collections 3.1157: */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 the168: * most recently used position (first).169: *170: * @param key the key171: * @return the mapped value, null if no match172: */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 update189: */190: protected void moveToMRU(LinkEntry entry) {191: if (entry.after != header) {192: modCount++;193: // remove194: entry.before.after = entry.after;195: entry.after.before = entry.before;196: // add first197: 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 list211: * using {@link #moveToMRU(AbstractLinkedMap.LinkEntry)}.212: *213: * @param entry the entry to update214: * @param newValue the new value to store215: */216: protected void updateEntry(HashEntry entry, Object newValue) {217: moveToMRU((LinkEntry) entry); // handles modCount218: 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 to225: * discard an entry or not using {@link #removeLRU(AbstractLinkedMap.LinkEntry)}.226: * <p>227: * From Commons Collections 3.1 this method uses {@link #isFull()} rather228: * 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 at232: * @param hashCode the hash code of the key to add233: * @param key the key to add234: * @param value the value to add235: */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 reuse282: * @param hashIndex the index into the data array to store at283: * @param hashCode the hash code of the key to add284: * @param key the key to add285: * @param value the value to add286: */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 table289: // remember that the parameters (except the first) refer to the new entry,290: // not the old one291: try {292: int removeIndex = hashIndex(entry.hashCode, data.length);293: HashEntry[] tmp = data; // may protect against some sync issues294: 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 entry309: 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 to326: * 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 entry330: * return true; // actually delete entry331: * }332: * </pre>333: * <p>334: * Alternatively, a subclass may choose to not remove the entry or selectively335: * 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 LRUMap340: * } else {341: * return true; // actually delete entry342: * }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 on347: * 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 removed354: */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 full364: */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 hold373: */374: public int maxSize() {375: return maxSize;376: }377:378: /**379: * Whether this LRUMap will scan until a removable entry is found when the380: * map is full.381: *382: * @return true if this map scans383: * @since Commons Collections 3.1384: */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 clone394: */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,直接用,好用且稳定。
相关文章推荐
- apache2.4配置php5.6.19出现“Cannot load F:/php5.6.19/php5apache2_4.dll into server”错误
- apache 连接数
- apache2 在ubuntu上的文件路径与常用命令
- Ubuntu下安装和配置Apache2
- apache mod_concatx模块 合并多个js/css 提高网页加载速度
- apache实现虚拟主机
- php安装后检测不成功,apache无法解析php
- java.lang.ClassNotFoundException: org.apache.struts2.dispatcher.FilterDispatcher
- 使用Apache ab工具对Apache服务器进行简单的压力测试
- 04_Apache Hadoop 生态系统
- php7+apache2.4 (Windows7下)安装
- Apache Maven 入门篇(下)
- 自己MARK一下:APACHE中用.htaccess去除单入口路径index.php
- Apache 403 错误解决方法-让别人可以访问你的服务器(转)
- Win7下Apache和tomca整合
- Ubuntu14.4下搭配WEB服务器(apache + php + mysql)
- How To Create a SSL Certificate on Apache for Debian 7 |htttps
- How To Create a SSL Certificate on Apache for Debian 8 htttps
- maven打包问题导致org.apache.ibatis.binding.BindingException: Invalid bound statement
- Apache shutdown unexpectedly启动错误解决方法