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.4
Show newest version
/*
 * 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).
 */
package org.codehaus.plexus.util;

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 Object 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 (Iterator i = map.entrySet().iterator(); i.hasNext(); ) { Map.Entry e = (Map.Entry) i.next(); addEntry(e.getKey(), e.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 Object remove(Object key) { EntryImpl entry = _entries[keyHash(key) & _mask]; while (entry != null) { if (key.equals(entry._key) ) { Object 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 paramters. 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 Object _key; /** * Holds the entry value (null when in pool). */ private Object _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 Object getKey() { return _key; } /** * Returns the value for this entry. * * @return the entry's value. */ public Object getValue() { return _value; } /** * Sets the value for this entry. * * @param value the new value. * @return the previous value. */ public Object setValue(Object value) { Object 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; } } }