📄 cachemap.java
字号:
/* CacheMap.java{{IS_NOTE Purpose: Description: History: 2001/11/23 15:26:21, Create, Tom M. Yeh.}}IS_NOTECopyright (C) 2001 Potix Corporation. All Rights Reserved.{{IS_RIGHT This program is distributed under GPL Version 2.0 in the hope that it will be useful, but WITHOUT ANY WARRANTY.}}IS_RIGHT*/package org.zkoss.util;import java.util.AbstractCollection;import java.util.Collection;import java.util.Map;import java.util.AbstractSet;import java.util.Set;import java.util.LinkedHashMap;import java.util.Iterator;import java.lang.ref.Reference;import java.lang.ref.WeakReference;import java.lang.ref.ReferenceQueue;import org.zkoss.lang.D;import org.zkoss.lang.Objects;import org.zkoss.util.logging.Log;/** * The cache map. The key-to-value mappings hold in this map is * temporary. They are removed when GC demanding memory and a * criteria is met. The criteria is whether the mapping is old enough * (called lifetime), or the upper bound is hit (called max-size). * * <p>The criteria can be changed by overriding {@link #canExpunge}. * When to check the criteria can be changed by overriding * {@link #shallExpunge}. * * <p>If the criteria is totally independent of GC, you could override * {@link #newQueue} to return null. Then, {@link #shallExpunge} * always returns true (rather than when GC is activated) -- of course, * you could override {@link #shallExpunge}, too. * * <p>The constructor doesn't provide parameter to set the lifetime * or maxsize. However, {@link #setLifetime} and {@link #setMaxSize} * return the map, so you can do:<br> * <code>Map map = new CacheMap().setLifetime(10000);</code> * * <p>It is very different from WeakHashMap: * * <ul> * <li>The mapping might be removed even if the key is hold somewhere * (i.e., strong reachable).</li> * <li>The mapping might not be removed when GC demanding memory * if the criteria doesn't meet.</li> * <li>It is not serializable.</li> * </ul> * * <p>Like other maps, it is not thread-safe. To get one, use * java.util.Collections.synchronizedMap. * * <p>Implementation Note: there is another version of CacheMap that * uses WeakReference for each value (refer to obsolete). * The drawback is that all mapping will be queued and need to be examined, * because GC tends to release all reference at once. * * <p>We don't use PhantomReference because it is still required to * re-create the reference after enqueued. * * @author tomyeh */public class CacheMap implements Map, java.io.Serializable, Cloneable { private static final long serialVersionUID = 20060622L; //private static final Log log = Log.lookup(CacheMap.class); /** The default minimal lifetime, unit=milliseconds. It is 30 minutes. */ public static final int DEFAULT_LIFETIME = 30 * 60 * 1000; /** The default maximal allowed size. It is 1024. */ public static final int DEFAULT_MAXSIZE = 1024; /** The map to store the mappings. */ private Map _map; //it is OK to serialized /** The minimal lifetime. */ private int _lifetime = DEFAULT_LIFETIME; /** The maximal allowed size. */ private int _maxsize = DEFAULT_MAXSIZE; /** The reference queue. */ private transient ReferenceQueue _que; /** The reference. */ private transient WeakReference _ref; /** A flag used for debug purpose. */ private transient boolean _inExpunge; /** The class to be hold in the reference (to know GC is demanding). */ private static class X { } /** The class to hold key/value. */ protected static final class Value implements java.io.Serializable, Cloneable { private Object value; private long access; //when the mapping is accessed /** Creates an instance to store in the map. */ private Value(Object value) { this.value = value; updateAccessTime(); } private final void updateAccessTime() { this.access = System.currentTimeMillis(); } //-- utilities--// /** Returns the value. */ public final Object getValue() { return this.value; } /** Returns the last access time. */ public final long getAccessTime() { return this.access; } //-- cloneable --// public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(); } } //-- Object --// public final String toString() { return "(" + this.value + '@' + this.access + ')'; } } //-- deriving to override --// /** * Called when a pair of key and value having been expunged. * This method is called after it is removed, so you could * add it back. * * <p>Default: does nothing */ protected void onExpunge(Value v) { } /** Returns by {@link #canExpunge} to denote it shall not be expunged. */ protected static final int EXPUNGE_NO = 0x0; /** Returns by {@link #canExpunge} to denote it shall be expunged. */ protected static final int EXPUNGE_YES = 0x1; //must not zero /** Returns by {@link #canExpunge} to denote the searching of the * next mapping shall continue. */ protected static final int EXPUNGE_CONTINUE = 0x0; /** Returns by {@link #canExpunge} to denote the searching of the * next mapping shall stop. */ protected static final int EXPUNGE_STOP = 0x2; //must not zero /** Returns whether it is time to expunge. * Once shallExpunge returns true, values are examined one-by-one thru * {@link #canExpunge}, and expunged if EXPUNGE_YES. * * <p>This implementation returns true only if {@link #newQueue} * returns null (in constructor) or GC was activated. * You might override it to enforce expunge besides GC. * * @see #canExpunge */ protected boolean shallExpunge() { return _que == null || _que.poll() != null; } /** * Tests whether certain value is OK to expunge. * * <p>Note: values are tested thru {@link #canExpunge} only if * {@link #shallExpunge} returns true. * * <p>Deriving classes might override this method to return different * value for different criteria. * * <p>The return value coulde be a combination of EXPUNGE_xxx. * One of EXPUNGE_YES and EXPUNGE_NO is returned to denote * whether to expunge the mapping. One of EXPUNGE_CONTINUE and * EXPUNGE_STOP is returned to denote whether to continue the * searching of the next mapping for expunging. * * <p>Normally, you return either (EXPUNGE_YES|EXPUNGE_CONTINUE) * or (EXPUNG_NO|EXPUNGE_STOP). * Notice that the mapping is queried in the last-access order. * Thus, you rarely needs to return (EXPUNGE_NO|EXPUNGE_CONTINUE) * unless the appropriate one might be out of this order. * * <p>This implementation compares the access time and size. * It returns (EXPUNGE_YES|EXPUNGE_CONTINUE) if OK, and * (EXPUNGE_NO|EXPUNGE_STOP) if not. * * @return a combination of EXPUNGE_xxx * @see #shallExpunge */ protected int canExpunge(Value v) { return _map.size() > getMaxSize() || (System.currentTimeMillis() - v.access) > getLifetime() ? (EXPUNGE_YES|EXPUNGE_CONTINUE): (EXPUNGE_NO|EXPUNGE_STOP); } private void expunge() { if (shallExpunge()) { if (_inExpunge) throw new IllegalStateException("expung in expung?"); _inExpunge = true; try { int k = 5; //to speed up, limit # to remove for (final Iterator it = _map.values().iterator(); --k >= 0 && it.hasNext();) { final Value v = (Value)it.next(); final int result = canExpunge(v); if ((result & EXPUNGE_YES) != 0) { //if (D.ON && log.debugable()) // log.debug("expunge: value="+v.value+" size="+_map.size()+"("+getMaxSize()+") time="+v.access+"("+getLifetime()+")"); it.remove(); onExpunge(v); } if ((result & EXPUNGE_STOP) != 0) break; //stop } newRef(); } finally { _inExpunge = false; } } } /** Creates the reference queue. * It is called only once in the constructor (so it is meaningless * to change the returned value after constructed). * * <p>Default: new ReferenceQueue();<br> * Override this method to return null if you want to expunge items * every time {@link #get} or {@link #put} is called -- not only GC * is activated. * In other words, if {@link #newQueue} returns null, {@link #shallExpunge} * always returns true (unless you override it too). */ protected ReferenceQueue newQueue() { return new ReferenceQueue(); } /** Re-create the reference so we can detect if GC was activated. */ private void newRef() { if (_que != null) _ref = new WeakReference(new X(), _que); } //-- constructors --// /** Constructs a cachemap by using LinkedHashMap internally. */ public CacheMap() { _map = new LinkedHashMap(16, 0.75f, true); init(); } /** Constructs a cachemap by using LinkedHashMap internally. */ public CacheMap(int cap) { _map = new LinkedHashMap(cap, 0.75f, true); init(); } /** Constructs a cachemap by using LinkedHashMap internally. */ public CacheMap(int cap, float load) { _map = new LinkedHashMap(cap, load, true); init(); } /** Initialization for contructor and de-serialized. */ private void init() { _que = newQueue(); newRef(); } //-- extra api --// /** * Gets the minimal lifetime, unit=milliseconds. * An mapping won't be removed by GC unless the minimal lifetime * or the maximal allowed size exceeds. * @see #getMaxSize */ public int getLifetime() { return _lifetime; } /** * Sets the minimal lifetime. Default: {@link #DEFAULT_LIFETIME}. * * @param lifetime the lifetime, unit=milliseconds; * if non-posive, they will be removed immediately. * @see #getLifetime */ public CacheMap setLifetime(int lifetime) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -