LruHashtable.java Source code

Java tutorial

Introduction

Here is the source code for LruHashtable.java

Source

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

}