All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.plexus.util.FastMap Maven / Gradle / Ivy

There is a newer version: 4.1.2
Show newest version
package org.codehaus.plexus.util;

/*
 * J.A.D.E. Java(TM) Addition to Default Environment.
 * Latest release available at http://jade.dautelle.com/
 * This class is public domain (not copyrighted).
 */

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * 

* This class represents a Map collection with real-time behavior. Unless the map's size exceeds its * current capacity, no dynamic memory allocation is ever performed and response time is extremely fast and * consistent. *

*

* Our benchmark indicates that {@link FastMap#put * FastMap.put(key, value)} is up to 5x faster than java.util.HashMap.put(key, value). This * difference is mostly due to the cost of the Map.Entry allocations that {@link FastMap} avoids by * recycling its entries (see note below). *

*

* {@link FastMap} has a predictable iteration order, which is the order in which keys were inserted into the map * (similar to java.util.LinkedHashMap collection class). *

*

* Applications may change the resizing policy of {@link FastMap} by overriding the {@link #sizeChanged} method. For * example, to improve predictability, automatic resizing can be disabled. *

*

* This implementation is not synchronized. Multiple threads accessing or modifying the collection must be synchronized * externally. *

*

* Note: To avoid dynamic memory allocations, {@link FastMap} maintains an internal pool of * Map.Entry objects. The size of the pool is determined by the map's capacity. When an entry is removed * from the map, it is automatically restored to the pool. *

*

* This class is public domain (not copyrighted). *

* * @author Jean-Marie Dautelle * @version 5.3, October 31 2003 */ public class FastMap implements Map, Cloneable, Serializable { /** * Holds the map's hash table. */ private transient EntryImpl[] _entries; /** * Holds the map's current capacity. */ private transient int _capacity; /** * Holds the hash code mask. */ private transient int _mask; /** * Holds the first pool entry (linked list). */ private transient EntryImpl _poolFirst; /** * Holds the first map entry (linked list). */ private transient EntryImpl _mapFirst; /** * Holds the last map entry (linked list). */ private transient EntryImpl _mapLast; /** * Holds the current size. */ private transient int _size; /** * Creates a {@link FastMap} with a capacity of 256 entries. */ public FastMap() { initialize( 256 ); } /** * Creates a {@link FastMap}, copy of the specified Map. If the specified map is not an instance of * {@link FastMap}, the newly created map has a capacity set to the specified map's size. The copy has the same * order as the original, regardless of the original map's implementation: * *
     *     TreeMap dictionary = ...;
     *     FastMap dictionaryLookup = new FastMap(dictionary);
     * 
* * @param map the map whose mappings are to be placed in this map. */ public FastMap( Map map ) { int capacity = ( map instanceof FastMap ) ? ( (FastMap) map ).capacity() : map.size(); initialize( capacity ); putAll( map ); } /** * Creates a {@link FastMap} with the specified capacity. Unless the capacity is exceeded, operations on this map do * not allocate entries. For optimum performance, the capacity should be of the same order of magnitude or larger * than the expected map's size. * * @param capacity the number of buckets in the hash table; it also defines the number of pre-allocated entries. */ public FastMap( int capacity ) { initialize( capacity ); } /** * Returns the number of key-value mappings in this {@link FastMap}. * * @return this map's size. */ public int size() { return _size; } /** * Returns the capacity of this {@link FastMap}. The capacity defines the number of buckets in the hash table, as * well as the maximum number of entries the map may contain without allocating memory. * * @return this map's capacity. */ public int capacity() { return _capacity; } /** * Indicates if this {@link FastMap} contains no key-value mappings. * * @return true if this map contains no key-value mappings; false otherwise. */ public boolean isEmpty() { return _size == 0; } /** * Indicates if this {@link FastMap} contains a mapping for the specified key. * * @param key the key whose presence in this map is to be tested. * @return true if this map contains a mapping for the specified key; false otherwise. * @throws NullPointerException if the key is null. */ public boolean containsKey( Object key ) { EntryImpl entry = _entries[keyHash( key ) & _mask]; while ( entry != null ) { if ( key.equals( entry._key ) ) { return true; } entry = entry._next; } return false; } /** * Indicates if this {@link FastMap} maps one or more keys to the specified value. * * @param value the value whose presence in this map is to be tested. * @return true if this map maps one or more keys to the specified value. * @throws NullPointerException if the key is null. */ public boolean containsValue( Object value ) { EntryImpl entry = _mapFirst; while ( entry != null ) { if ( value.equals( entry._value ) ) { return true; } entry = entry._after; } return false; } /** * Returns the value to which this {@link FastMap} maps the specified key. * * @param key the key whose associated value is to be returned. * @return the value to which this map maps the specified key, or null if there is no mapping for the * key. * @throws NullPointerException if key is null. */ public V get( Object key ) { EntryImpl entry = _entries[keyHash( key ) & _mask]; while ( entry != null ) { if ( key.equals( entry._key ) ) { return entry._value; } entry = entry._next; } return null; } /** * Returns the entry with the specified key. * * @param key the key whose associated entry is to be returned. * @return the entry for the specified key or null if none. */ public Map.Entry getEntry( Object key ) { EntryImpl entry = _entries[keyHash( key ) & _mask]; while ( entry != null ) { if ( key.equals( entry._key ) ) { return entry; } entry = entry._next; } return null; } /** * Associates the specified value with the specified key in this {@link FastMap}. If the {@link FastMap} previously * contained a mapping for this key, the old value is replaced. * * @param key the key with which the specified value is to be associated. * @param value the value to be associated with the specified key. * @return the previous value associated with specified key, or null if there was no mapping for key. A * null return can also indicate that the map previously associated null with the * specified key. * @throws NullPointerException if the key is null. */ public Object put( Object key, Object value ) { EntryImpl entry = _entries[keyHash( key ) & _mask]; while ( entry != null ) { if ( key.equals( entry._key ) ) { Object prevValue = entry._value; entry._value = value; return prevValue; } entry = entry._next; } // No previous mapping. addEntry( key, value ); return null; } /** * Copies all of the mappings from the specified map to this {@link FastMap}. * * @param map the mappings to be stored in this map. * @throws NullPointerException the specified map is null, or the specified map contains * null keys. */ public void putAll( Map map ) { for ( Entry entry : map.entrySet() ) { addEntry( entry.getKey(), entry.getValue() ); } } /** * Removes the mapping for this key from this {@link FastMap} if present. * * @param key the key whose mapping is to be removed from the map. * @return previous value associated with specified key, or null if there was no mapping for key. A * null return can also indicate that the map previously associated null with the * specified key. * @throws NullPointerException if the key is null. */ public V remove( Object key ) { EntryImpl entry = _entries[keyHash( key ) & _mask]; while ( entry != null ) { if ( key.equals( entry._key ) ) { V prevValue = entry._value; removeEntry( entry ); return prevValue; } entry = entry._next; } return null; } /** * Removes all mappings from this {@link FastMap}. */ public void clear() { // Clears all keys, values and buckets linked lists. for ( EntryImpl entry = _mapFirst; entry != null; entry = entry._after ) { entry._key = null; entry._value = null; entry._before = null; entry._next = null; if ( entry._previous == null ) { // First in bucket. _entries[entry._index] = null; } else { entry._previous = null; } } // Recycles all entries. if ( _mapLast != null ) { _mapLast._after = _poolFirst; // Connects to pool. _poolFirst = _mapFirst; _mapFirst = null; _mapLast = null; _size = 0; sizeChanged(); } } /** * Changes the current capacity of this {@link FastMap}. If the capacity is increased, new entries are allocated and * added to the pool. If the capacity is decreased, entries from the pool are deallocated (and are eventually * garbage collected). The capacity also determined the number of buckets for the hash table. * * @param newCapacity the new capacity of this map. */ public void setCapacity( int newCapacity ) { if ( newCapacity > _capacity ) { // Capacity increases. for ( int i = _capacity; i < newCapacity; i++ ) { EntryImpl entry = new EntryImpl(); entry._after = _poolFirst; _poolFirst = entry; } } else if ( newCapacity < _capacity ) { // Capacity decreases. for ( int i = newCapacity; ( i < _capacity ) && ( _poolFirst != null ); i++ ) { // Disconnects the entry for gc to do its work. EntryImpl entry = _poolFirst; _poolFirst = entry._after; entry._after = null; // All pointers are now null! } } // Find a power of 2 >= capacity int tableLength = 16; while ( tableLength < newCapacity ) { tableLength <<= 1; } // Checks if the hash table has to be re-sized. if ( _entries.length != tableLength ) { _entries = new EntryImpl[tableLength]; _mask = tableLength - 1; // Repopulates the hash table. EntryImpl entry = _mapFirst; while ( entry != null ) { int index = keyHash( entry._key ) & _mask; entry._index = index; // Connects to bucket. entry._previous = null; // Resets previous. EntryImpl next = _entries[index]; entry._next = next; if ( next != null ) { next._previous = entry; } _entries[index] = entry; entry = entry._after; } } _capacity = newCapacity; } /** * Returns a shallow copy of this {@link FastMap}. The keys and the values themselves are not cloned. * * @return a shallow copy of this map. */ public Object clone() { try { FastMap clone = (FastMap) super.clone(); clone.initialize( _capacity ); clone.putAll( this ); return clone; } catch ( CloneNotSupportedException e ) { // Should not happen, since we are Cloneable. throw new InternalError(); } } /** * Compares the specified object with this {@link FastMap} for equality. Returns true if the given * object is also a map and the two maps represent the same mappings (regardless of collection iteration order). * * @param obj the object to be compared for equality with this map. * @return true if the specified object is equal to this map; false otherwise. */ public boolean equals( Object obj ) { if ( obj == this ) { return true; } else if ( obj instanceof Map ) { Map that = (Map) obj; if ( this.size() == that.size() ) { EntryImpl entry = _mapFirst; while ( entry != null ) { if ( !that.entrySet().contains( entry ) ) { return false; } entry = entry._after; } return true; } else { return false; } } else { return false; } } /** * Returns the hash code value for this {@link FastMap}. * * @return the hash code value for this map. */ public int hashCode() { int code = 0; EntryImpl entry = _mapFirst; while ( entry != null ) { code += entry.hashCode(); entry = entry._after; } return code; } /** * Returns a String representation of this {@link FastMap}. * * @return this.entrySet().toString(); */ public String toString() { return entrySet().toString(); } /** * Returns a collection view of the values contained in this {@link FastMap}. The collection is backed by the map, * so changes to the map are reflected in the collection, and vice-versa. The collection supports element removal, * which removes the corresponding mapping from this map, via the Iterator.remove, * Collection.remove, removeAll, retainAll, and clear * operations. It does not support the add or addAll operations. * * @return a collection view of the values contained in this map. */ public Collection values() { return _values; } private transient Values _values; private class Values extends AbstractCollection { public Iterator iterator() { return new Iterator() { EntryImpl after = _mapFirst; EntryImpl before; public void remove() { removeEntry( before ); } public boolean hasNext() { return after != null; } public Object next() { before = after; after = after._after; return before._value; } }; } public int size() { return _size; } public boolean contains( Object o ) { return containsValue( o ); } public void clear() { FastMap.this.clear(); } } /** * Returns a collection view of the mappings contained in this {@link FastMap}. Each element in the returned * collection is a Map.Entry. The collection is backed by the map, so changes to the map are reflected * in the collection, and vice-versa. The collection supports element removal, which removes the corresponding * mapping from this map, via the Iterator.remove, Collection.remove, * removeAll, retainAll, and clear operations. It does not support the * add or addAll operations. * * @return a collection view of the mappings contained in this map. */ public Set entrySet() { return _entrySet; } private transient EntrySet _entrySet; private class EntrySet extends AbstractSet { public Iterator iterator() { return new Iterator() { EntryImpl after = _mapFirst; EntryImpl before; public void remove() { removeEntry( before ); } public boolean hasNext() { return after != null; } public Object next() { before = after; after = after._after; return before; } }; } public int size() { return _size; } public boolean contains( Object obj ) { // Optimization. if ( obj instanceof Map.Entry ) { Map.Entry entry = (Map.Entry) obj; Map.Entry mapEntry = getEntry( entry.getKey() ); return entry.equals( mapEntry ); } else { return false; } } public boolean remove( Object obj ) { // Optimization. if ( obj instanceof Map.Entry ) { Map.Entry entry = (Map.Entry) obj; EntryImpl mapEntry = (EntryImpl) getEntry( entry.getKey() ); if ( ( mapEntry != null ) && ( entry.getValue() ).equals( mapEntry._value ) ) { removeEntry( mapEntry ); return true; } } return false; } } /** * Returns a set view of the keys contained in this {@link FastMap}. The set is backed by the map, so changes to the * map are reflected in the set, and vice-versa. The set supports element removal, which removes the corresponding * mapping from this map, via the Iterator.remove, Collection.remove, * removeAll, retainAll, and clear operations. It does not support the * add or addAll operations. * * @return a set view of the keys contained in this map. */ public Set keySet() { return _keySet; } private transient KeySet _keySet; private class KeySet extends AbstractSet { public Iterator iterator() { return new Iterator() { EntryImpl after = _mapFirst; EntryImpl before; public void remove() { removeEntry( before ); } public boolean hasNext() { return after != null; } public Object next() { before = after; after = after._after; return before._key; } }; } public int size() { return _size; } public boolean contains( Object obj ) { // Optimization. return FastMap.this.containsKey( obj ); } public boolean remove( Object obj ) { // Optimization. return FastMap.this.remove( obj ) != null; } public void clear() { // Optimization. FastMap.this.clear(); } } /** * This methods is being called when the size of this {@link FastMap} has changed. The default behavior is to double * the map's capacity when the map's size reaches the current map's capacity. Sub-class may override this method to * implement custom resizing policies or to disable automatic resizing. For example: * *
     * Map fixedCapacityMap = new FastMap( 256 )
     * {
     *     protected sizeChanged()
     *     {
     *         // Do nothing, automatic resizing disabled.
     *     }
     * };
     * 
* * @see #setCapacity */ protected void sizeChanged() { if ( size() > capacity() ) { setCapacity( capacity() * 2 ); } } /** * Returns the hash code for the specified key. The formula being used is identical to the formula used by * java.util.HashMap (ensures similar behavior for ill-conditioned hashcode keys). * * @param key the key to calculate the hashcode for. * @return the hash code for the specified key. */ private static int keyHash( Object key ) { // From HashMap.hash(Object) function. int hashCode = key.hashCode(); hashCode += ~( hashCode << 9 ); hashCode ^= ( hashCode >>> 14 ); hashCode += ( hashCode << 4 ); hashCode ^= ( hashCode >>> 10 ); return hashCode; } /** * Adds a new entry for the specified key and value. * * @param key the entry's key. * @param value the entry's value. */ private void addEntry( Object key, Object value ) { EntryImpl entry = _poolFirst; if ( entry != null ) { _poolFirst = entry._after; entry._after = null; } else { // Pool empty. entry = new EntryImpl(); } // Setup entry parameters. entry._key = key; entry._value = value; int index = keyHash( key ) & _mask; entry._index = index; // Connects to bucket. EntryImpl next = _entries[index]; entry._next = next; if ( next != null ) { next._previous = entry; } _entries[index] = entry; // Connects to collection. if ( _mapLast != null ) { entry._before = _mapLast; _mapLast._after = entry; } else { _mapFirst = entry; } _mapLast = entry; // Updates size. _size++; sizeChanged(); } /** * Removes the specified entry from the map. * * @param entry the entry to be removed. */ private void removeEntry( EntryImpl entry ) { // Removes from bucket. EntryImpl previous = entry._previous; EntryImpl next = entry._next; if ( previous != null ) { previous._next = next; entry._previous = null; } else { // First in bucket. _entries[entry._index] = next; } if ( next != null ) { next._previous = previous; entry._next = null; } // Else do nothing, no last pointer. // Removes from collection. EntryImpl before = entry._before; EntryImpl after = entry._after; if ( before != null ) { before._after = after; entry._before = null; } else { // First in collection. _mapFirst = after; } if ( after != null ) { after._before = before; } else { // Last in collection. _mapLast = before; } // Clears value and key. entry._key = null; entry._value = null; // Recycles. entry._after = _poolFirst; _poolFirst = entry; // Updates size. _size--; sizeChanged(); } /** * Initializes this instance for the specified capacity. Once initialized, operations on this map should not create * new objects (unless the map's size exceeds the specified capacity). * * @param capacity the initial capacity. */ private void initialize( int capacity ) { // Find a power of 2 >= capacity int tableLength = 16; while ( tableLength < capacity ) { tableLength <<= 1; } // Allocates hash table. _entries = new EntryImpl[tableLength]; _mask = tableLength - 1; _capacity = capacity; _size = 0; // Allocates views. _values = new Values(); _entrySet = new EntrySet(); _keySet = new KeySet(); // Resets pointers. _poolFirst = null; _mapFirst = null; _mapLast = null; // Allocates entries. for ( int i = 0; i < capacity; i++ ) { EntryImpl entry = new EntryImpl(); entry._after = _poolFirst; _poolFirst = entry; } } /** * Requires special handling during de-serialization process. * * @param stream the object input stream. * @throws IOException if an I/O error occurs. * @throws ClassNotFoundException if the class for the object de-serialized is not found. */ private void readObject( ObjectInputStream stream ) throws IOException, ClassNotFoundException { int capacity = stream.readInt(); initialize( capacity ); int size = stream.readInt(); for ( int i = 0; i < size; i++ ) { Object key = stream.readObject(); Object value = stream.readObject(); addEntry( key, value ); } } /** * Requires special handling during serialization process. * * @param stream the object output stream. * @throws IOException if an I/O error occurs. */ private void writeObject( ObjectOutputStream stream ) throws IOException { stream.writeInt( _capacity ); stream.writeInt( _size ); int count = 0; EntryImpl entry = _mapFirst; while ( entry != null ) { stream.writeObject( entry._key ); stream.writeObject( entry._value ); count++; entry = entry._after; } if ( count != _size ) { throw new IOException( "FastMap Corrupted" ); } } /** * This class represents a {@link FastMap} entry. */ private static final class EntryImpl implements Map.Entry { /** * Holds the entry key (null when in pool). */ private K _key; /** * Holds the entry value (null when in pool). */ private V _value; /** * Holds the bucket index (undefined when in pool). */ private int _index; /** * Holds the previous entry in the same bucket (null when in pool). */ private EntryImpl _previous; /** * Holds the next entry in the same bucket (null when in pool). */ private EntryImpl _next; /** * Holds the entry added before this entry (null when in pool). */ private EntryImpl _before; /** * Holds the entry added after this entry or the next available entry when in pool. */ private EntryImpl _after; /** * Returns the key for this entry. * * @return the entry's key. */ public K getKey() { return _key; } /** * Returns the value for this entry. * * @return the entry's value. */ public V getValue() { return _value; } /** * Sets the value for this entry. * * @param value the new value. * @return the previous value. */ public V setValue( V value ) { V old = _value; _value = value; return old; } /** * Indicates if this entry is considered equals to the specified entry. * * @param that the object to test for equality. * @return true if both entry are considered equal; false otherwise. */ public boolean equals( Object that ) { if ( that instanceof Map.Entry ) { Map.Entry entry = (Map.Entry) that; return ( _key.equals( entry.getKey() ) ) && ( ( _value != null ) ? _value.equals( entry.getValue() ) : ( entry.getValue() == null ) ); } else { return false; } } /** * Returns the hash code for this entry. * * @return this entry's hash code. */ public int hashCode() { return _key.hashCode() ^ ( ( _value != null ) ? _value.hashCode() : 0 ); } /** * Returns the text representation of this entry. * * @return this entry's textual representation. */ public String toString() { return _key + "=" + _value; } } }