Java tutorial
/* * 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). * * This class was added from J.A.D.E. directly to avoid adding the jade.jar * which was causing a conflict with parsing XML menus in FreeHep for some reason */ 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.ListIterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; /** * <p> This class represents a <code>Map</code> 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 * <b>extremely fast</b> and <b>consistent</b>.</p> * * <p> Our <a href="http://jade.dautelle.com/doc/benchmark.txt">benchmark</a> * indicates that {@link FastMap#put FastMap.put(key, value)} is up to * <b>5x faster</b> than <code>java.util.HashMap.put(key, value)</code>. * This difference is mostly due to the cost of the <code>Map.Entry</code> * allocations that {@link FastMap} avoids by recycling its entries * (see note below).</p> * * <p> {@link FastMap} has a predictable iteration order, which is the order * in which keys were inserted into the map (similar to * <code>java.util.LinkedHashMap</code> collection class). * A bi-directional list iterator over the map entries is also * {@link #fastIterator provided}, this iterator can be moved * to the {@link FastIterator#toFirst first} or to the * {@link FastIterator#toLast last} entry for unlimited reuse.</p> * * <p> Applications may change the resizing policy of {@link FastMap} * by overriding the {@link #sizeChanged} method. For example, to reduce * memory footprint, the map's capacity could be maitained at 50% of * the current map's size.</p> * * <p> This implementation is not synchronized. Multiple threads accessing * or modifying the collection must be synchronized externally.</p> * * <p> <b>Note:</b> To avoid dynamic memory allocations, {@link FastMap} * maintains an internal pool of <code>Map.Entry</code> 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.</p> * * <p><i> This class is <b>public domain</b> (not copyrighted).</i></p> * * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a> * @version 6.0, January 18 2004 */ 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 <code>16</code> entries. */ public FastMap() { initialize(16); } /** * Creates a {@link FastMap}, copy of the specified <code>Map</code>. * 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:<pre> * TreeMap dictionary = ...; * FastMap dictionaryLookup = new FastMap(dictionary); * </pre> * * @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 <code>true</code> if this map contains no key-value mappings; * <code>false</code> 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 <code>true</code> if this map contains a mapping for the * specified key; <code>false</code> otherwise. * @throws NullPointerException if the key is <code>null</code>. */ 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 <code>true</code> if this map maps one or more keys to the * specified value. * @throws NullPointerException if the key is <code>null</code>. */ 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 <code>null</code> if there is no mapping for the key. * @throws NullPointerException if key is <code>null</code>. */ 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 <code>null</code> if none. */ public 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 <code>null</code> if there was no mapping for key. * A <code>null</code> return can also indicate that the map * previously associated <code>null</code> with the specified key. * @throws NullPointerException if the key is <code>null</code>. */ 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; } /** * Returns a reusable {@link FastIterator} over this {@link FastMap} entries * (unique instance per map). For example:<pre> * // Iteration without memory allocation! * for (FastIterator i=map.fastIterator().toFirst(); i.hasNext();) { * Entry entry = i.nextEntry(); * ... * }</pre> * * @return an iterator which can be reset for reuse over and over. * @see FastMap.FastIterator */ public FastIterator fastIterator() { return _fastIterator; } private final FastIterator _fastIterator = new FastIterator(); /** * 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 <code>null</code>, or * the specified map contains <code>null</code> keys. */ public void putAll(Map map) { for (Iterator i = map.entrySet().iterator(); i.hasNext();) { Entry e = (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 <code>null</code> if there was no mapping for key. * A <code>null</code> return can also indicate that the map * previously associated <code>null</code> with the specified key. * @throws NullPointerException if the key is <code>null</code>. */ 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 garbage collected eventually). 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 <code>true</code> 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 <code>true</code> if the specified object is equal to this map; * <code>false</code> 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 <code>String</code> representation of this {@link FastMap}. * * @return <code>this.entrySet().toString();</code> */ 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 * <code>Iterator.remove</code>, <code>Collection.remove</code>, * <code>removeAll</code>, <code>retainAll</code>, * and <code>clear</code> operations. It does not support the * <code>add</code> or <code>addAll</code> 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() { if (before != null) { removeEntry(before); } else { throw new IllegalStateException(); } } public boolean hasNext() { return after != null; } public Object next() { if (after != null) { before = after; after = after._after; return before._value; } else { throw new NoSuchElementException(); } } }; } 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 * <code>Map.Entry</code>. 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 * <code>Iterator.remove</code>, <code>Collection.remove</code>, * <code>removeAll</code>, <code>retainAll</code>, * and <code>clear</code> operations. It does not support the * <code>add</code> or <code>addAll</code> 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() { if (before != null) { removeEntry(before); } else { throw new IllegalStateException(); } } public boolean hasNext() { return after != null; } public Object next() { if (after != null) { before = after; after = after._after; return before; } else { throw new NoSuchElementException(); } } }; } public int size() { return _size; } public boolean contains(Object obj) { // Optimization. if (obj instanceof Entry) { Entry entry = (Entry) obj; Entry mapEntry = getEntry(entry.getKey()); return entry.equals(mapEntry); } else { return false; } } public boolean remove(Object obj) { // Optimization. if (obj instanceof Entry) { Entry entry = (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 * <code>Iterator.remove</code>, <code>Collection.remove</code>, * <code>removeAll</code>, <code>retainAll</code>, * and <code>clear</code> operations. It does not support the * <code>add</code> or <code>addAll</code> 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() { if (before != null) { removeEntry(before); } else { throw new IllegalStateException(); } } public boolean hasNext() { return after != null; } public Object next() { if (after != null) { before = after; after = after._after; return before._key; } else { throw new NoSuchElementException(); } } }; } 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 exceeds the current map's capacity. * Sub-class may override this method to implement custom resizing * policies or to disable automatic resizing. For example:<pre> * Map fixedCapacityMap = new FastMap(256) { * protected sizeChanged() { * // Do nothing, automatic resizing disabled. * } * };</pre> * @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 <code>java.util.HashMap</code> * (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 inner class represents a reusable list iterator over * {@link FastMap} entries. This iterator is bi-directional and can be * directly moved to the {@link #toFirst first} or {@link #toLast last} * entry. For example:<pre> * for (FastIterator i=map.fastIterator().toFirst(); i.hasNext();) { * Entry entry = i.nextEntry(); * ... * }</pre> * {@link #set setting} or {@link #add adding} new entries is not * supported. */ public final class FastIterator implements ListIterator { EntryImpl after = _mapFirst; EntryImpl before; int nextIndex = 0; public FastIterator toFirst() { after = _mapFirst; before = null; nextIndex = 0; return this; } public FastIterator toLast() { after = null; before = _mapLast; nextIndex = _size; return this; } public boolean hasNext() { return after != null; } public Entry nextEntry() { if (after != null) { nextIndex++; before = after; after = after._after; return before; } else { throw new NoSuchElementException(); } } public Object next() { return nextEntry(); } public boolean hasPrevious() { return before != null; } public Entry previousEntry() { if (before != null) { nextIndex--; after = before; before = before._after; return after; } else { throw new NoSuchElementException(); } } public Object previous() { return previousEntry(); } public int nextIndex() { return nextIndex; } public int previousIndex() { return nextIndex - 1; } public void remove() { if (before != null) { removeEntry(before); } else { throw new IllegalStateException(); } } public void set(Object o) { throw new UnsupportedOperationException(); } public void add(Object o) { throw new UnsupportedOperationException(); } } /** * This class represents a {@link FastMap} entry. */ private static final class EntryImpl implements 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 <code>true<code> if both entry are considered equal; * <code>false<code> otherwise. */ public boolean equals(Object that) { if (that instanceof Entry) { Entry entry = (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; } } }