/////////////////////////////////////////////////////////////////////////////// // Copyright (c) 2001, Eric D. Friedman All Rights Reserved. // Copyright (c) 2009, Rob Eden All Rights Reserved. // Copyright (c) 2009, Jeff Randall All Rights Reserved. // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package gnu.trove.impl.hash; import gnu.trove.impl.Constants; import gnu.trove.impl.HashFunctions; import gnu.trove.impl.PrimeFinder; import java.io.Externalizable; import java.io.ObjectOutput; import java.io.IOException; import java.io.ObjectInput; /** * Base class for hashtables that use open addressing to resolve * collisions. * * Created: Wed Nov 28 21:11:16 2001 * * @author Eric D. Friedman * @author Rob Eden (auto-compaction) * @author Jeff Randall * * @version $Id: THash.java,v 1.1.2.4 2010/03/02 00:55:34 robeden Exp $ */ abstract public class THash implements Externalizable { static final long serialVersionUID = -1792948471915530295L; /** the load above which rehashing occurs. */ protected static final float DEFAULT_LOAD_FACTOR = Constants.DEFAULT_LOAD_FACTOR; /** * the default initial capacity for the hash table. This is one * less than a prime value because one is added to it when * searching for a prime capacity to account for the free slot * required by open addressing. Thus, the real default capacity is * 11. */ protected static final int DEFAULT_CAPACITY = Constants.DEFAULT_CAPACITY; /** the current number of occupied slots in the hash. */ protected transient int _size; /** the current number of free slots in the hash. */ protected transient int _free; /** * Determines how full the internal table can become before * rehashing is required. This must be a value in the range: 0.0 < * loadFactor < 1.0. The default value is 0.5, which is about as * large as you can get in open addressing without hurting * performance. Cf. Knuth, Volume 3., Chapter 6. */ protected float _loadFactor; /** * The maximum number of elements allowed without allocating more * space. */ protected int _maxSize; /** The number of removes that should be performed before an auto-compaction occurs. */ protected int _autoCompactRemovesRemaining; /** * The auto-compaction factor for the table. * * @see #setAutoCompactionFactor */ protected float _autoCompactionFactor; /** @see #tempDisableAutoCompaction */ protected transient boolean _autoCompactTemporaryDisable = false; /** * Creates a new <code>THash</code> instance with the default * capacity and load factor. */ public THash() { this( DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR ); } /** * Creates a new <code>THash</code> instance with a prime capacity * at or near the specified capacity and with the default load * factor. * * @param initialCapacity an <code>int</code> value */ public THash( int initialCapacity ) { this( initialCapacity, DEFAULT_LOAD_FACTOR ); } /** * Creates a new <code>THash</code> instance with a prime capacity * at or near the minimum needed to hold <tt>initialCapacity</tt> * elements with load factor <tt>loadFactor</tt> without triggering * a rehash. * * @param initialCapacity an <code>int</code> value * @param loadFactor a <code>float</code> value */ public THash( int initialCapacity, float loadFactor ) { super(); _loadFactor = loadFactor; // Through testing, the load factor (especially the default load factor) has been // found to be a pretty good starting auto-compaction factor. _autoCompactionFactor = loadFactor; setUp( HashFunctions.fastCeil( initialCapacity / loadFactor ) ); } /** * Tells whether this set is currently holding any elements. * * @return a <code>boolean</code> value */ public boolean isEmpty() { return 0 == _size; } /** * Returns the number of distinct elements in this collection. * * @return an <code>int</code> value */ public int size() { return _size; } /** @return the current physical capacity of the hash table. */ abstract public int capacity(); /** * Ensure that this hashtable has sufficient capacity to hold * <tt>desiredCapacity<tt> <b>additional</b> elements without * requiring a rehash. This is a tuning method you can call * before doing a large insert. * * @param desiredCapacity an <code>int</code> value */ public void ensureCapacity( int desiredCapacity ) { if ( desiredCapacity > ( _maxSize - size() ) ) { rehash( PrimeFinder.nextPrime( HashFunctions.fastCeil( ( desiredCapacity + size() ) / _loadFactor ) + 1 ) ); computeMaxSize( capacity() ); } } /** * Compresses the hashtable to the minimum prime size (as defined * by PrimeFinder) that will hold all of the elements currently in * the table. If you have done a lot of <tt>remove</tt> * operations and plan to do a lot of queries or insertions or * iteration, it is a good idea to invoke this method. Doing so * will accomplish two things: * <p/> * <ol> * <li> You'll free memory allocated to the table but no * longer needed because of the remove()s.</li> * <p/> * <li> You'll get better query/insert/iterator performance * because there won't be any <tt>REMOVED</tt> slots to skip * over when probing for indices in the table.</li> * </ol> */ public void compact() { // need at least one free spot for open addressing rehash( PrimeFinder.nextPrime( HashFunctions.fastCeil( size() / _loadFactor ) + 1 ) ); computeMaxSize( capacity() ); // If auto-compaction is enabled, re-determine the compaction interval if ( _autoCompactionFactor != 0 ) { computeNextAutoCompactionAmount( size() ); } } /** * The auto-compaction factor controls whether and when a table performs a * {@link #compact} automatically after a certain number of remove operations. * If the value is non-zero, the number of removes that need to occur for * auto-compaction is the size of table at the time of the previous compaction * (or the initial capacity) multiplied by this factor. * <p/> * Setting this value to zero will disable auto-compaction. * * @param factor a <tt>float</tt> that indicates the auto-compaction factor */ public void setAutoCompactionFactor( float factor ) { if ( factor < 0 ) { throw new IllegalArgumentException( "Factor must be >= 0: " + factor ); } _autoCompactionFactor = factor; } /** * @see #setAutoCompactionFactor * * @return a <<tt>float</tt> that represents the auto-compaction factor. */ public float getAutoCompactionFactor() { return _autoCompactionFactor; } /** * This simply calls {@link #compact compact}. It is included for * symmetry with other collection classes. Note that the name of this * method is somewhat misleading (which is why we prefer * <tt>compact</tt>) as the load factor may require capacity above * and beyond the size of this collection. * * @see #compact */ public final void trimToSize() { compact(); } /** * Delete the record at <tt>index</tt>. Reduces the size of the * collection by one. * * @param index an <code>int</code> value */ protected void removeAt( int index ) { _size--; // If auto-compaction is enabled, see if we need to compact if ( _autoCompactionFactor != 0 ) { _autoCompactRemovesRemaining--; if ( !_autoCompactTemporaryDisable && _autoCompactRemovesRemaining <= 0 ) { // Do the compact // NOTE: this will cause the next compaction interval to be calculated compact(); } } } /** Empties the collection. */ public void clear() { _size = 0; _free = capacity(); } /** * initializes the hashtable to a prime capacity which is at least * <tt>initialCapacity + 1</tt>. * * @param initialCapacity an <code>int</code> value * @return the actual capacity chosen */ protected int setUp( int initialCapacity ) { int capacity; capacity = PrimeFinder.nextPrime( initialCapacity ); computeMaxSize( capacity ); computeNextAutoCompactionAmount( initialCapacity ); return capacity; } /** * Rehashes the set. * * @param newCapacity an <code>int</code> value */ protected abstract void rehash( int newCapacity ); /** * Temporarily disables auto-compaction. MUST be followed by calling * {@link #reenableAutoCompaction}. */ public void tempDisableAutoCompaction() { _autoCompactTemporaryDisable = true; } /** * Re-enable auto-compaction after it was disabled via * {@link #tempDisableAutoCompaction()}. * * @param check_for_compaction True if compaction should be performed if needed * before returning. If false, no compaction will be * performed. */ public void reenableAutoCompaction( boolean check_for_compaction ) { _autoCompactTemporaryDisable = false; if ( check_for_compaction && _autoCompactRemovesRemaining <= 0 && _autoCompactionFactor != 0 ) { // Do the compact // NOTE: this will cause the next compaction interval to be calculated compact(); } } /** * Computes the values of maxSize. There will always be at least * one free slot required. * * @param capacity an <code>int</code> value */ protected void computeMaxSize( int capacity ) { // need at least one free slot for open addressing _maxSize = Math.min( capacity - 1, (int) ( capacity * _loadFactor ) ); _free = capacity - _size; // reset the free element count } /** * Computes the number of removes that need to happen before the next auto-compaction * will occur. * * @param size an <tt>int</tt> that sets the auto-compaction limit. */ protected void computeNextAutoCompactionAmount( int size ) { if ( _autoCompactionFactor != 0 ) { // NOTE: doing the round ourselves has been found to be faster than using // Math.round. _autoCompactRemovesRemaining = (int) ( ( size * _autoCompactionFactor ) + 0.5f ); } } /** * After an insert, this hook is called to adjust the size/free * values of the set and to perform rehashing if necessary. * * @param usedFreeSlot the slot */ protected final void postInsertHook( boolean usedFreeSlot ) { if ( usedFreeSlot ) { _free--; } // rehash whenever we exhaust the available space in the table if ( ++_size > _maxSize || _free == 0 ) { // choose a new capacity suited to the new state of the table // if we've grown beyond our maximum size, double capacity; // if we've exhausted the free spots, rehash to the same capacity, // which will free up any stale removed slots for reuse. int newCapacity = _size > _maxSize ? PrimeFinder.nextPrime( capacity() << 1 ) : capacity(); rehash( newCapacity ); computeMaxSize( capacity() ); } } protected int calculateGrownCapacity() { return capacity() << 1; } public void writeExternal( ObjectOutput out ) throws IOException { // VERSION out.writeByte( 0 ); // LOAD FACTOR out.writeFloat( _loadFactor ); // AUTO COMPACTION LOAD FACTOR out.writeFloat( _autoCompactionFactor ); } public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException { // VERSION in.readByte(); // LOAD FACTOR float old_factor = _loadFactor; _loadFactor = in.readFloat(); // AUTO COMPACTION LOAD FACTOR _autoCompactionFactor = in.readFloat(); // If we change the laod factor from the default, re-setup if ( old_factor != _loadFactor ) { setUp( (int) Math.ceil( DEFAULT_CAPACITY / _loadFactor ) ); } } }// THash