Java tutorial
// LruHashtable - a Hashtable that expires least-recently-used objects // // Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. // // Visit the ACME Labs Java page for up-to-date versions of this and other // fine Java utilities: http://www.acme.com/java/ // // moved to the net.matuschek.util package by Daniel Matuschek // import java.util.Enumeration; import java.util.Hashtable; /** * A Hashtable that expires least-recently-used objects. * * <p>Use just like java.util.Hashtable, except that the initial-capacity * parameter is required. Instead of growing bigger than that size, * it will throw out objects that haven't been looked at in a while. * </p> * * @author Jef Poskanzer * @author Daniel Matuschek * @version $Id: LruHashtable.java,v 1.3 2002/05/31 14:45:56 matuschd Exp $ * * @see java.util.Hashtable */ public class LruHashtable extends Hashtable { // Number of buckets. private static final int nBuckets = 2; // Load factor. private float loadFactor; // When count exceeds this threshold, expires the old table. private int threshold; // Capacity of each bucket. private int eachCapacity; // The tables. private Hashtable oldTable; private Hashtable newTable; /// Constructs a new, empty hashtable with the specified initial // capacity and the specified load factor. // Unlike a plain Hashtable, an LruHashtable will never grow or // shrink from this initial capacity. // @param initialCapacity the initial number of buckets // @param loadFactor a number between 0.0 and 1.0, it defines // the threshold for expiring old entries // @exception IllegalArgumentException If the initial capacity // is less than or equal to zero. // @exception IllegalArgumentException If the load factor is // less than or equal to zero. public LruHashtable(int initialCapacity, float loadFactor) { // We have to call a superclass constructor, but we're not actually // going to use it at all. The only reason we want to extend Hashtable // is for type conformance. So, make a parent hash table of minimum // size and then ignore it. super(1); if (initialCapacity <= 0 || loadFactor <= 0.0) throw new IllegalArgumentException(); this.loadFactor = loadFactor; threshold = (int) (initialCapacity * loadFactor) - 1; eachCapacity = initialCapacity / nBuckets + 1; oldTable = new Hashtable(eachCapacity, loadFactor); newTable = new Hashtable(eachCapacity, loadFactor); } /// Constructs a new, empty hashtable with the specified initial // capacity. // Unlike a plain Hashtable, an LruHashtable will never grow or // shrink from this initial capacity. // @param initialCapacity the initial number of buckets public LruHashtable(int initialCapacity) { this(initialCapacity, 0.75F); } /// Returns the number of elements contained in the hashtable. public int size() { return newTable.size() + oldTable.size(); } /// Returns true if the hashtable contains no elements. public boolean isEmpty() { return size() == 0; } /// Returns an enumeration of the hashtable's keys. // @see LruHashtable#elements // @see Enumeration public synchronized Enumeration keys() { return new LruHashtableEnumerator(oldTable, newTable, true); } /// Returns an enumeration of the elements. Use the Enumeration methods // on the returned object to fetch the elements sequentially. // @see LruHashtable#keys // @see Enumeration public synchronized Enumeration elements() { return new LruHashtableEnumerator(oldTable, newTable, false); } /// Returns true if the specified object is an element of the hashtable. // This operation is more expensive than the containsKey() method. // @param value the value that we are looking for // @exception NullPointerException If the value being searched // for is equal to null. // @see LruHashtable#containsKey public synchronized boolean contains(Object value) { if (newTable.contains(value)) return true; if (oldTable.contains(value)) { // We would like to move the object from the old table to the // new table. However, we need keys to re-add the objects, and // there's no good way to find all the keys for the given object. // We'd have to enumerate through all the keys and check each // one. Yuck. For now we just punt. Anyway, contains() is // probably not a commonly-used operation. return true; } return false; } /// Returns true if the collection contains an element for the key. // @param key the key that we are looking for // @see LruHashtable#contains public synchronized boolean containsKey(Object key) { if (newTable.containsKey(key)) return true; if (oldTable.containsKey(key)) { // Move object from old table to new table. Object value = oldTable.get(key); newTable.put(key, value); oldTable.remove(key); return true; } return false; } /// Gets the object associated with the specified key in the // hashtable. // @param key the specified key // @returns the element for the key or null if the key // is not defined in the hash table. // @see LruHashtable#put public synchronized Object get(Object key) { Object value; value = newTable.get(key); if (value != null) return value; value = oldTable.get(key); if (value != null) { // Move object from old table to new table. newTable.put(key, value); oldTable.remove(key); return value; } return null; } /// Puts the specified element into the hashtable, using the specified // key. The element may be retrieved by doing a get() with the same key. // The key and the element cannot be null. // @param key the specified key in the hashtable // @param value the specified element // @exception NullPointerException If the value of the element // is equal to null. // @see LruHashtable#get // @return the old value of the key, or null if it did not have one. public synchronized Object put(Object key, Object value) { Object oldValue = newTable.put(key, value); if (oldValue != null) return oldValue; oldValue = oldTable.get(key); if (oldValue != null) oldTable.remove(key); else { if (size() >= threshold) { // Rotate the tables. oldTable = newTable; newTable = new Hashtable(eachCapacity, loadFactor); } } return oldValue; } /// Removes the element corresponding to the key. Does nothing if the // key is not present. // @param key the key that needs to be removed // @return the value of key, or null if the key was not found. public synchronized Object remove(Object key) { Object oldValue = newTable.remove(key); if (oldValue == null) oldValue = oldTable.remove(key); return oldValue; } /// Clears the hash table so that it has no more elements in it. public synchronized void clear() { newTable.clear(); oldTable.clear(); } /// Creates a clone of the hashtable. A shallow copy is made, // the keys and elements themselves are NOT cloned. This is a // relatively expensive operation. public synchronized Object clone() { LruHashtable n = (LruHashtable) super.clone(); n.newTable = (Hashtable) n.newTable.clone(); n.oldTable = (Hashtable) n.oldTable.clone(); return n; } // toString() can be inherited. } class LruHashtableEnumerator implements Enumeration { Enumeration oldEnum; Enumeration newEnum; boolean old; LruHashtableEnumerator(Hashtable oldTable, Hashtable newTable, boolean keys) { if (keys) { oldEnum = oldTable.keys(); newEnum = newTable.keys(); } else { oldEnum = oldTable.elements(); newEnum = newTable.elements(); } old = true; } public boolean hasMoreElements() { boolean r; if (old) { r = oldEnum.hasMoreElements(); if (!r) { old = false; r = newEnum.hasMoreElements(); } } else r = newEnum.hasMoreElements(); return r; } public Object nextElement() { if (old) return oldEnum.nextElement(); return newEnum.nextElement(); } }