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. *
* * @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(); } }