Mega Code Archive

 
Categories / Java / Collections Data Structure
 

Lru Hashtable

// 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();   }    }