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

javolution.util.stripped.FastMap Maven / Gradle / Ivy

/*
 * Javolution - Java(TM) Solution for Real-Time and Embedded Systems
 * Copyright (C) 2006 - Javolution (http://javolution.org/)
 * All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software is
 * freely granted, provided that this notice is preserved.
 */
package javolution.util.stripped;

import javolution.util.stripped.FastCollection.Record;

import java.io.*;
import java.util.*;
import java.util.logging.Logger;


/**
 * 

This class represents a hash map with real-time behavior; * smooth capacity increase and thread-safe without external * synchronization when {@link #shared shared}.

* *

*

{@link FastMap} has a predictable iteration order, which is the order in * which keys are inserted into the map (similar to * java.util.LinkedHashMap collection class). If the map is * marked {@link #shared shared} then all operations are * thread-safe including iterations over the map's collections. * Unlike ConcurrentHashMap, {@link #get(Object) access} never * blocks; retrieval reflects the map state not older than the last time the * accessing threads have been synchronized (for multi-processors systems * synchronizing ensures that the CPU internal cache is not stale). * In most application it is not a problem because thread synchronization * is done at high level (e.g. scheduler) and threads run freely * (and quickly) until the next synchronization point. In some cases the * "happen before" guarantee is necessary (e.g. to ensure unicity) and * threads have to be synchronized explicitly. Whenever possible such * synchronization should be performed on the key object itself and * not the whole map. For example:[code] * FastMap sparseVector = new FastMap().shared() * ... // Put * synchronized(index) { // javolution.util.Index instances are unique. * sparseVector.put(index, value); * } * ... // Get * synchronized(index) { // Blocking only when accessing the same index. * value = sparseVector.get(index); // Latest value guaranteed. * }[/code]

*

*

{@link FastMap} may use custom key comparators; the default comparator is * either {@link FastComparator#DIRECT DIRECT} or * {@link FastComparator#REHASH REHASH} based upon the current Javolution * Configuration. Users may explicitly set the key comparator to * {@link FastComparator#DIRECT DIRECT} for optimum performance * when the hash codes are well distributed for all run-time platforms * (e.g. calculated hash codes).

*

*

Custom key comparators are extremely useful for value retrieval when * map's keys and argument keys are not of the same class (such as * {@link String} and Text * ({@link FastComparator#LEXICAL LEXICAL})), to substitute more efficient * hash code calculations ({@link FastComparator#STRING STRING}) * or for identity maps ({@link FastComparator#IDENTITY IDENTITY}):[code] * FastMap identityMap = new FastMap().setKeyComparator(FastComparator.IDENTITY); * [/code]

*

*

{@link FastMap.Entry} can quickly be iterated over (forward or backward) * without using iterators. For example:[code] * FastMap map = ...; * for (FastMap.Entry e = map.head(), end = map.tail(); (e = e.getNext()) != end;) { * String key = e.getKey(); // No typecast necessary. * Thread value = e.getValue(); // No typecast necessary. * }[/code]

*

*

Custom map implementations may override the {@link #newEntry} method * in order to return their own {@link Entry} implementation (with * additional fields for example).

*

*

{@link FastMap} are reusable; they maintain an * internal pool of Map.Entry objects. When an entry is removed * from a map, it is automatically restored to its pool (unless the map * is shared in which case the removed entry is candidate for garbage * collection as it cannot be safely recycled).

*

*

{@link #shared() Shared} maps do not use internal synchronization, except in case of * concurrent modifications of the map structure (entry added/deleted). * Reads and iterations are never synchronized and never blocking. * With regards to the memory model, shared maps are equivalent to shared * non-volatile variables (no "happen before" guarantee). They can be used * as very efficient lookup tables. For example:[code] * public class Unit { * static FastMap labels = new FastMap().shared(); * ... * public String toString() { * String label = labels.get(this); // No synchronization. * return label != null ? label : makeLabel(); * } * }[/code]

*

*

Implementation Note: To maintain time-determinism, rehash/resize * is performed only when the map's size is small (see chart). For large * maps (size > 512), the map is divided recursively into (64) * smaller sub-maps. The cost of the dispatching (based upon hashcode * value) has been measured to be at most 20% of the access time * (and most often way less).

* * @author Jean-Marie Dautelle * @version 5.2, September 11, 2007 */ public class FastMap implements Map { public static final Logger logger = Logger.getLogger("javolution.util"); // We do a full resize (and rehash) only when the capacity is less than C1. // For large maps we dispatch to sub-maps. private static final int B0 = 4; // Initial capacity in bits. private static final int C0 = 1 << B0; // Initial capacity (16) private static final int B1 = 10; // Entries array resize limit in bits. private static final int C1 = 1 << B1; // Entries array resize limit (1024). private static final int B2 = B1 - B0; // Sub-maps array length in bits. private static final int C2 = 1 << B2; // Sub-maps array length (64). /** * Holds the head entry to which the first entry attaches. * The head entry never changes (entries always added last). */ private transient Entry _head; /** * Holds the tail entry to which the last entry attaches. * The tail entry changes as entries are added/removed. */ private transient Entry _tail; /** * Holds the map's entries. */ private transient Entry[] _entries; /** * Holds the number of user entry in the entry table. */ private transient int _entryCount; /** * Holds the number of NULL (when entry removed). The number has to be * taken into account to clean-up the table if too many NULL are present. */ private transient int _nullCount; /** * Holds sub-maps (for large collection). */ private transient FastMap[] _subMaps; /** * Indicates if sub-maps are active. */ private transient boolean _useSubMaps; /** * The hash shift (for sub-maps to discard bits already taken into account). */ private transient int _keyShift; /** * Holds the values view. */ private transient Values _values; /** * Holds the key set view. */ private transient KeySet _keySet; /** * Holds the entry set view. */ private transient EntrySet _entrySet; /** * Holds the unmodifiable view. */ private transient Map _unmodifiable; /** * Holds the key comparator. */ private transient FastComparator _keyComparator; /** * Indicates if key comparator is direct. */ private transient boolean _isDirectKeyComparator; /** * Holds the value comparator. */ private transient FastComparator _valueComparator; /** * Indicates if this map is shared (thread-safe). */ private transient boolean _isShared; /** * Creates a map whose capacity increment smoothly without large resize * operations. */ public FastMap() { this(4); } /** * Creates a map of specified maximum size (a full resize may occur if the * specififed capacity is exceeded). * * @param capacity the maximum capacity. */ public FastMap(int capacity) { setKeyComparator(FastComparator.DEFAULT); setValueComparator(FastComparator.DEFAULT); setup(capacity); } private void setup(int capacity) { int tableLength = C0; while (tableLength < capacity) { tableLength <<= 1; } _entries = (Entry[]) new Entry[tableLength << 1]; _head = newEntry(); _tail = newEntry(); _head._next = _tail; _tail._previous = _head; Entry previous = _tail; for (int i = 0; i++ < capacity;) { Entry newEntry = newEntry(); newEntry._previous = previous; previous._next = newEntry; previous = newEntry; } } /** * Creates a map containing the specified entries, in the order they * are returned by the map iterator. * * @param map the map whose entries are to be placed into this map. */ public FastMap(Map map) { this(map.size()); putAll(map); } /** * Used solely for sub-maps (we don't need head or tail just the table). */ private FastMap(Entry[] entries) { _entries = entries; } /** * Returns a potentially recycled map instance. * * @return a new, preallocated or recycled map instance. */ public static FastMap newInstance() { return (FastMap) new FastMap(); } /** * Returns the reverse mapping of this map (for which the keys are this * map's values and the values are this map's keys). * * @return the map having for keys this map values and for values * this map's key. */ public final FastMap reverse() { FastMap map = new FastMap(); for (Entry e = _head, n = _tail; (e = e._next) != n;) { map.put(e._value, e._key); } return map; } /** * Returns the head entry of this map. * * @return the entry such as head().getNext() holds * the first map entry. */ public final Entry head() { return _head; } /** * Returns the tail entry of this map. * * @return the entry such as tail().getPrevious() * holds the last map entry. */ public final Entry tail() { return _tail; } /** * Returns the number of key-value mappings in this {@link FastMap}. *

*

Note: If concurrent updates are performed, application should not * rely upon the size during iterations.

* * @return this map's size. */ public final int size() { if (!_useSubMaps) { return _entryCount; } int sum = 0; for (int i = 0; i < _subMaps.length;) { sum += _subMaps[i++].size(); } return sum; } /** * Indicates if this map contains no key-value mappings. * * @return true if this map contains no key-value mappings; * false otherwise. */ public final boolean isEmpty() { return _head._next == _tail; } /** * Indicates if this map 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 final boolean containsKey(Object key) { return getEntry(key) != null; } /** * Indicates if this map associates 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 final boolean containsValue(Object value) { return values().contains(value); } /** * Returns the value to which this map associates the specified key. * This method is always thread-safe regardless whether or not the map * is marked {@link #isShared() shared}. * * @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 final V get(Object key) { Entry entry = getEntry(key); return (entry != null) ? entry._value : null; } /** * Returns the entry with the specified key. * This method is always thread-safe without synchronization. * * @param key the key whose associated entry is to be returned. * @return the entry for the specified key or null if none. */ public final Entry getEntry(Object key) { return getEntry(key, _isDirectKeyComparator ? key.hashCode() : _keyComparator.hashCodeOf(key)); } private final Entry getEntry(Object key, int keyHash) { final FastMap map = getSubMap(keyHash); final Entry[] entries = map._entries; // Atomic. final int mask = entries.length - 1; for (int i = keyHash >> map._keyShift; ; i++) { Entry entry = entries[i & mask]; if (entry == null) { return null; } if ((key == entry._key) || ((keyHash == entry._keyHash) && (_isDirectKeyComparator ? key.equals(entry._key) : _keyComparator.areEqual(key, entry._key)))) { return entry; } } } private final FastMap getSubMap(int keyHash) { return _useSubMaps ? _subMaps[keyHash & (C2 - 1)].getSubMap(keyHash >> B2) : this; } /** * Associates the specified value with the specified key in this map. * If this map previously contained a mapping for this key, the old value * is replaced. For {@link #isShared() shared} map, internal synchronization * is performed only when new entries are created. * * @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 final V put(K key, V value) { return (V) put(key, value, _isDirectKeyComparator ? key.hashCode() : _keyComparator.hashCodeOf(key), _isShared, false, false); } private final Object put(Object key, Object value, int keyHash, boolean concurrent, boolean noReplace, boolean returnEntry) { final FastMap map = getSubMap(keyHash); final Entry[] entries = map._entries; // Atomic. final int mask = entries.length - 1; int slot = -1; for (int i = keyHash >> map._keyShift; ; i++) { Entry entry = entries[i & mask]; if (entry == null) { slot = slot < 0 ? i & mask : slot; break; } else if (entry == Entry.NULL) { slot = slot < 0 ? i & mask : slot; } else if ((key == entry._key) || ((keyHash == entry._keyHash) && (_isDirectKeyComparator ? key.equals(entry._key) : _keyComparator.areEqual(key, entry._key)))) { if (noReplace) { return returnEntry ? entry : entry._value; } Object prevValue = entry._value; entry._value = value; return returnEntry ? entry : prevValue; } } // Add new entry (synchronize if concurrent). if (concurrent) { synchronized (this) { return put(key, value, keyHash, false, noReplace, returnEntry); } } // Setup entry. final Entry entry; if (!_isShared) { entry = _tail; entry._key = key; entry._value = value; entry._keyHash = keyHash; if (entry._next == null) { createNewEntries(); } entries[slot] = entry; map._entryCount += ONE_VOLATILE; // Prevents reordering. _tail = _tail._next; } else { // keep _tail as the same object // check entry caches if (_tail._next == null) { createNewEntries(); } // assign entry entry = _tail._next; // step forward in the entry cache _tail._next = entry._next; // populate entry entry._key = key; entry._value = value; entry._keyHash = keyHash; entry._next = _tail; entry._previous = _tail._previous; // backwards // set the hash table slots entries[slot] = entry; map._entryCount += ONE_VOLATILE; // Prevents reordering. // insert into chain at correct location entry._next._previous = entry; // backwards entry._previous._next = entry; // forwards } if (map._entryCount + map._nullCount > (entries.length >> 1)) { // Table more than half empty. map.resizeTable(_isShared); } return returnEntry ? entry : null; } private void createNewEntries() { // Increase the number of entries. Entry previous = _tail; for (int i = 0; i < 8; i++) { // Creates 8 entries at a time. Entry newEntry = newEntry(); newEntry._previous = previous; previous._next = newEntry; previous = newEntry; } } // This method is called only on final sub-maps. private void resizeTable(final boolean isShared) { // Reset the NULL count (we don't copy Entry.NULL). final int nullCount = _nullCount; _nullCount = 0; // Check if we can just cleanup (remove NULL entries). if (nullCount > _entryCount) { // Yes. if (isShared) { // Replaces with a new table. Entry[] tmp = new Entry[_entries.length]; copyEntries(_entries, tmp, _entries.length); _entries = tmp; } else { // We need a temporary buffer. Object[] tmp = (Object[]) new Object[_entries.length]; System.arraycopy(_entries, 0, tmp, 0, _entries.length); FastMap.reset(_entries); // Ok not shared. copyEntries(tmp, _entries, _entries.length); } return; } // Resize if size is small. int tableLength = _entries.length << 1; if (tableLength <= C1) { // Ok to resize. Entry[] tmp = new Entry[tableLength]; copyEntries(_entries, tmp, _entries.length); _entries = tmp; return; } // No choice but to use sub-maps. if (_subMaps == null) { // Creates sub-maps. _subMaps = newSubMaps(tableLength >> (B2 - 1)); } // Copies the current entries to sub-maps. for (int i = 0; i < _entries.length;) { Entry entry = _entries[i++]; if ((entry == null) || (entry == Entry.NULL)) { continue; } FastMap subMap = _subMaps[(entry._keyHash >> _keyShift) & (C2 - 1)]; subMap.mapEntry(entry); if (((subMap._entryCount + subMap._nullCount) << 1) >= subMap._entries.length) { // Serious problem submap already full, don't use submap just resize. logger.warning("Unevenly distributed hash code - Degraded Preformance"); Entry[] tmp = new Entry[tableLength]; copyEntries(_entries, tmp, _entries.length); _entries = tmp; _subMaps = null; // Discards sub-maps. return; } } if (isShared) { // clear the entries which now are held by submaps FastMap.reset(_entries); _nullCount = 0; _entryCount = 0; } _useSubMaps = ONE_VOLATILE == 1 ? true : false; // Prevents reordering. } private FastMap[] newSubMaps(int capacity) { FastMap[] subMaps = new FastMap[C2]; for (int i = 0; i < C2; i++) { FastMap subMap = new FastMap(new Entry[capacity]); subMap._keyShift = B2 + _keyShift; subMaps[i] = subMap; } return subMaps; } // Adds the specified entry to this map table. private void mapEntry(Entry entry) { final int mask = _entries.length - 1; for (int i = entry._keyHash >> _keyShift; ; i++) { Entry e = _entries[i & mask]; if (e == null) { _entries[i & mask] = entry; break; } } _entryCount++; } // The destination table must be empty. private void copyEntries(Object[] from, Entry[] to, int count) { final int mask = to.length - 1; for (int i = 0; i < count;) { Entry entry = (Entry) from[i++]; if ((entry == null) || (entry == Entry.NULL)) { continue; } for (int j = entry._keyHash >> _keyShift; ; j++) { Entry tmp = to[j & mask]; if (tmp == null) { to[j & mask] = entry; break; } } } } /** * Associates the specified value with the specified key in this map and * returns the corresponding entry. * * @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 entry being added. * @throws NullPointerException if the key is null. */ public final Entry putEntry(K key, V value) { return (Entry) put(key, value, _isDirectKeyComparator ? key.hashCode() : _keyComparator.hashCodeOf(key), _isShared, false, true); } /** * Copies all of the mappings from the specified map to this map. * * @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 final void putAll(Map map) { for (Iterator i = map.entrySet().iterator(); i.hasNext();) { Map.Entry e = (Map.Entry) i.next(); put(e.getKey(), e.getValue()); } } /** * Associates the specified value only if the specified key is not already * associated. This is equivalent to:[code] * if (!map.containsKey(key)) * return map.put(key, value); * else * return map.get(key);[/code] * except that for shared maps the action is performed atomically. * For shared maps, this method guarantees that if two threads try to * put the same key concurrently only one of them will succeed. * * @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 final V putIfAbsent(K key, V value) { return (V) put(key, value, _isDirectKeyComparator ? key.hashCode() : _keyComparator.hashCodeOf(key), _isShared, true, false); } /** * Removes the entry for the specified key if present. The entry * is recycled if the map is not marked as {@link #isShared shared}; * otherwise the entry is candidate for garbage collection. *

*

Note: Shared maps in ImmortalMemory (e.g. static) should not remove * their entries as it could cause a memory leak (ImmortalMemory * is never garbage collected), instead they should set their * entry values to null.

* * @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 final V remove(Object key) { return (V) remove(key, _isDirectKeyComparator ? key.hashCode() : _keyComparator.hashCodeOf(key), _isShared); } private final Object remove(Object key, int keyHash, boolean concurrent) { final FastMap map = getSubMap(keyHash); final Entry[] entries = map._entries; // Atomic. final int mask = entries.length - 1; for (int i = keyHash >> map._keyShift; ; i++) { Entry entry = entries[i & mask]; if (entry == null) { return null; } // No entry. if ((key == entry._key) || ((keyHash == entry._keyHash) && (_isDirectKeyComparator ? key.equals(entry._key) : _keyComparator.areEqual(key, entry._key)))) { // Found the entry. if (concurrent) { synchronized (this) { return remove(key, keyHash, false); } } // Detaches entry from list. entry._previous._next = entry._next; entry._next._previous = entry._previous; // Removes from table. entries[i & mask] = Entry.NULL; map._nullCount++; map._entryCount--; Object prevValue = entry._value; if (!_isShared) { // Clears key/value and recycle. entry._key = null; entry._value = null; final Entry next = _tail._next; entry._previous = _tail; entry._next = next; _tail._next = entry; if (next != null) { next._previous = entry; } } else { // do nothing, preserving the iterator-free iterations of other threads } return prevValue; } } } /** *

Sets the shared status of this map (whether the map is thread-safe * or not). Shared maps are typically used for lookup table (e.g. static * instances in ImmortalMemory). They support concurrent access * (e.g. iterations) without synchronization, the maps updates * themselves are synchronized internally.

*

Unlike ConcurrentHashMap access to a shared map never * blocks. Retrieval reflects the map state not older than the last * time the accessing thread has been synchronized (for multi-processors * systems synchronizing ensures that the CPU internal cache is not * stale).

* * @return this */ public FastMap shared() { _isShared = true; return this; } /** * @deprecated Replaced by {@link #shared} */ public FastMap setShared(boolean isShared) { _isShared = isShared; return this; } /** * Indicates if this map supports concurrent operations without * synchronization (default unshared). * * @return true if this map is thread-safe; false * otherwise. */ public boolean isShared() { return _isShared; } /** * Sets the key comparator for this fast map. * * @param keyComparator the key comparator. * @return this */ public FastMap setKeyComparator( FastComparator keyComparator) { _keyComparator = keyComparator; _isDirectKeyComparator = (keyComparator == FastComparator.DIRECT) || ((_keyComparator == FastComparator.DEFAULT) && !FastComparator._Rehash); return this; } /** * Returns the key comparator for this fast map. * * @return the key comparator. */ public FastComparator getKeyComparator() { return _keyComparator; } /** * Sets the value comparator for this map. * * @param valueComparator the value comparator. * @return this */ public FastMap setValueComparator( FastComparator valueComparator) { _valueComparator = valueComparator; return this; } /** * Returns the value comparator for this fast map. * * @return the value comparator. */ public FastComparator getValueComparator() { return _valueComparator; } /** * Removes all map's entries. The entries are removed and recycled; * unless this map is {@link #isShared shared} in which case the entries * are candidate for garbage collection. *

*

Note: Shared maps in ImmortalMemory (e.g. static) should not remove * their entries as it could cause a memory leak (ImmortalMemory * is never garbage collected), instead they should set their * entry values to null.

*/ public final void clear() { if (_isShared) { clearShared(); return; } // Clears keys, values and recycle entries. for (Entry e = _head, end = _tail; (e = e._next) != end;) { e._key = null; e._value = null; } _tail = _head._next; // Reuse linked list of entries. clearTables(); } private void clearTables() { if (_useSubMaps) { for (int i = 0; i < C2;) { _subMaps[i++].clearTables(); } _useSubMaps = false; } FastMap.reset(_entries); _nullCount = 0; _entryCount = 0; } private synchronized void clearShared() { // We do not modify the linked list of entries (e.g. key, values) // Concurrent iterations can still proceed unaffected. // The linked list fragment is detached from the map and will be // garbage collected once all concurrent iterations are completed. _head._next = _tail; _tail._previous = _head; // We also detach the main entry table and sub-maps. _entries = (Entry[]) new Entry[C0]; if (_useSubMaps) { _useSubMaps = false; _subMaps = newSubMaps(C0); } _entryCount = 0; _nullCount = 0; } /** * Compares the specified object with this map 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; return this.entrySet().equals(that.entrySet()); } else { return false; } } /** * Returns the hash code value for this map. * * @return the hash code value for this map. */ public int hashCode() { int code = 0; for (Entry e = _head, end = _tail; (e = e._next) != end;) { code += e.hashCode(); } return code; } /** * Returns a new entry for this map; this method can be overriden by * custom map implementations. * * @return a new entry. */ protected Entry newEntry() { return new Entry(); } /** * Prints the current statistics on this map. * This method may help identify poorly defined hash functions. * The average distance should be less than 20% (most of the entries * are in their slots or close). * * @param out the stream to use for output (e.g. System.out) */ public void printStatistics(PrintStream out) { long sum = this.getSumDistance(); int size = this.size(); int avgDistancePercent = size != 0 ? (int) (100 * sum / size) : 0; synchronized (out) { out.print("SIZE: " + size); out.print(", ENTRIES: " + getCapacity()); out.print(", SLOTS: " + getTableLength()); out.print(", USE SUB-MAPS: " + _useSubMaps); out.print(", SUB-MAPS DEPTH: " + getSubMapDepth()); out.print(", NULL COUNT: " + _nullCount); out.print(", IS SHARED: " + _isShared); out.print(", AVG DISTANCE: " + avgDistancePercent + "%"); out.print(", MAX DISTANCE: " + getMaximumDistance()); out.println(); } } private int getTableLength() { if (_useSubMaps) { int sum = 0; for (int i = 0; i < C2; i++) { sum += _subMaps[i].getTableLength(); } return sum; } else { return _entries.length; } } private int getCapacity() { int capacity = 0; for (Entry e = _head; (e = e._next) != null;) { capacity++; } return capacity - 1; } private int getMaximumDistance() { int max = 0; if (_useSubMaps) { for (int i = 0; i < C2; i++) { int subMapMax = _subMaps[i].getMaximumDistance(); max = Math.max(max, subMapMax); } return max; } for (int i = 0; i < _entries.length; i++) { Entry entry = _entries[i]; if ((entry != null) && (entry != Entry.NULL)) { int slot = (entry._keyHash >> _keyShift) & (_entries.length - 1); int distanceToSlot = i - slot; if (distanceToSlot < 0) { distanceToSlot += _entries.length; } if (distanceToSlot > max) { max = distanceToSlot; } } } return max; } private long getSumDistance() { long sum = 0; if (_useSubMaps) { for (int i = 0; i < C2; i++) { sum += _subMaps[i].getSumDistance(); } return sum; } for (int i = 0; i < _entries.length; i++) { Entry entry = _entries[i]; if ((entry != null) && (entry != Entry.NULL)) { int slot = (entry._keyHash >> _keyShift) & (_entries.length - 1); int distanceToSlot = i - slot; if (distanceToSlot < 0) { distanceToSlot += _entries.length; } sum += distanceToSlot; } } return sum; } private int getSubMapDepth() { if (_useSubMaps) { int depth = 0; for (int i = 0; i < C2; i++) { int subMapDepth = _subMaps[i].getSubMapDepth(); depth = Math.max(depth, subMapDepth); } return depth + 1; } else { return 0; } } /** * Returns a {@link FastCollection} view of the values contained in this * map. 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 * (instance of {@link FastCollection}). */ public final Collection values() { if (_values == null) { _values = new Values(); } return _values; } private final class Values extends FastCollection { public int size() { return FastMap.this.size(); } public void clear() { FastMap.this.clear(); } public Record head() { return FastMap.this._head; } public Record tail() { return FastMap.this._tail; } public Object valueOf(Record record) { return ((Entry) record)._value; } public void delete(Record record) { FastMap.this.remove(((Entry) record).getKey()); } public FastComparator getValueComparator() { return _valueComparator; } public Iterator iterator() { return ValueIterator.valueOf(FastMap.this); } } // Value iterator optimization. private static final class ValueIterator implements Iterator { private FastMap _map; private Entry _current; private Entry _next; private Entry _tail; public static ValueIterator valueOf(FastMap map) { ValueIterator iterator = new ValueIterator(); iterator._map = map; iterator._next = map._head._next; iterator._tail = map._tail; return iterator; } private ValueIterator() { } public boolean hasNext() { return (_next != _tail); } public Object next() { if (_next == _tail) { throw new NoSuchElementException(); } _current = _next; _next = _next._next; return _current._value; } public void remove() { if (_current != null) { _next = _current._next; _map.remove(_current._key); _current = null; } else { throw new IllegalStateException(); } } } /** * Returns a {@link FastCollection} view of the mappings contained in this * map. Each element in the returned collection is a * FastMap.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 * (instance of {@link FastCollection}). */ public final Set> entrySet() { if (_entrySet == null) { _entrySet = new EntrySet(); } return _entrySet; } private final class EntrySet extends FastCollection implements Set { public int size() { return FastMap.this.size(); } public void clear() { FastMap.this.clear(); } public boolean contains(Object obj) { // Optimization. if (obj instanceof Map.Entry) { Map.Entry thatEntry = (Map.Entry) obj; Entry thisEntry = getEntry(thatEntry.getKey()); if (thisEntry == null) { return false; } return _valueComparator.areEqual(thisEntry.getValue(), thatEntry.getValue()); } else { return false; } } public Record head() { return _head; } public Record tail() { return _tail; } public Object valueOf(Record record) { return (Map.Entry) record; } public void delete(Record record) { FastMap.this.remove(((Entry) record).getKey()); } public FastComparator getValueComparator() { return _entryComparator; } private final FastComparator _entryComparator = new FastComparator() { public boolean areEqual(Object o1, Object o2) { if ((o1 instanceof Map.Entry) && (o2 instanceof Map.Entry)) { Map.Entry e1 = (Map.Entry) o1; Map.Entry e2 = (Map.Entry) o2; return _keyComparator.areEqual(e1.getKey(), e2.getKey()) && _valueComparator.areEqual(e1.getValue(), e2.getValue()); } return (o1 == null) && (o2 == null); } public int compare(Object o1, Object o2) { return _keyComparator.compare(o1, o2); } public int hashCodeOf(Object obj) { Map.Entry entry = (Map.Entry) obj; return _keyComparator.hashCodeOf(entry.getKey()) + _valueComparator.hashCodeOf(entry.getValue()); } }; public Iterator iterator() { return EntryIterator.valueOf(FastMap.this); } } // Entry iterator optimization. private static final class EntryIterator implements Iterator { private FastMap _map; private Entry _current; private Entry _next; private Entry _tail; public static EntryIterator valueOf(FastMap map) { EntryIterator iterator = new EntryIterator(); iterator._map = map; iterator._next = map._head._next; iterator._tail = map._tail; return iterator; } private EntryIterator() { } public boolean hasNext() { return (_next != _tail); } public Object next() { if (_next == _tail) { throw new NoSuchElementException(); } _current = _next; _next = _next._next; return _current; } public void remove() { if (_current != null) { _next = _current._next; _map.remove(_current._key); _current = null; } else { throw new IllegalStateException(); } } } /** * Returns a {@link FastCollection} view of the keys contained in this * map. 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 * (instance of {@link FastCollection}). */ public final Set keySet() { if (_keySet == null) { _keySet = new KeySet(); } return _keySet; } private final class KeySet extends FastCollection implements Set { public int size() { return FastMap.this.size(); } public void clear() { FastMap.this.clear(); } public boolean contains(Object obj) { // Optimization. return FastMap.this.containsKey(obj); } public boolean remove(Object obj) { // Optimization. return FastMap.this.remove(obj) != null; } public Record head() { return FastMap.this._head; } public Record tail() { return FastMap.this._tail; } public Object valueOf(Record record) { return ((Entry) record)._key; } public void delete(Record record) { FastMap.this.remove(((Entry) record).getKey()); } public FastComparator getValueComparator() { return _keyComparator; } public Iterator iterator() { return KeyIterator.valueOf(FastMap.this); } } // Entry iterator optimization. private static final class KeyIterator implements Iterator { private FastMap _map; private Entry _current; private Entry _next; private Entry _tail; public static KeyIterator valueOf(FastMap map) { KeyIterator iterator = new KeyIterator(); iterator._map = map; iterator._next = map._head._next; iterator._tail = map._tail; return iterator; } private KeyIterator() { } public boolean hasNext() { return (_next != _tail); } public Object next() { if (_next == _tail) { throw new NoSuchElementException(); } _current = _next; _next = _next._next; return _current._key; } public void remove() { if (_current != null) { _next = _current._next; _map.remove(_current._key); _current = null; } else { throw new IllegalStateException(); } } } /** * Returns the unmodifiable view associated to this map. * Attempts to modify the returned map or to directly access its * (modifiable) map entries (e.g. unmodifiable().entrySet()) * result in an {@link UnsupportedOperationException} being thrown. * Unmodifiable {@link FastCollection} views of this map keys and values * are nonetheless obtainable (e.g. unmodifiable().keySet(), * unmodifiable().values()). * * @return an unmodifiable view of this map. */ public final Map unmodifiable() { if (_unmodifiable == null) { _unmodifiable = new Unmodifiable(); } return _unmodifiable; } // Implements Reusable. public void reset() { _isShared = false; // A shared map can only be reset if no thread use it. clear(); // In which case, it is safe to recycle the entries. setKeyComparator(FastComparator.DEFAULT); setValueComparator(FastComparator.DEFAULT); } /** * 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 { setKeyComparator((FastComparator) stream.readObject()); setValueComparator((FastComparator) stream.readObject()); _isShared = stream.readBoolean(); final int size = stream.readInt(); setup(size); for (int i = 0; i < size; i++) { K key = (K) stream.readObject(); V value = (V) stream.readObject(); put(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.writeObject(getKeyComparator()); stream.writeObject(getValueComparator()); stream.writeBoolean(_isShared); stream.writeInt(size()); for (Entry e = _head, end = _tail; (e = e._next) != end;) { stream.writeObject(e._key); stream.writeObject(e._value); } } /** * This class represents a {@link FastMap} entry. * Custom {@link FastMap} may use a derived implementation. * For example:[code] * static class MyMap extends FastMap { * protected MyEntry newEntry() { * return new MyEntry(); * } * class MyEntry extends Entry { * X xxx; // Additional entry field (e.g. cross references). * } * }[/code] */ public static class Entry implements Map.Entry, Record { /** * Holds NULL entries (to fill empty hole). */ public static final Entry NULL = new Entry(); /** * Holds the next node. */ private Entry _next; /** * Holds the previous node. */ private Entry _previous; /** * Holds the entry key. */ private K _key; /** * Holds the entry value. */ private V _value; /** * Holds the key hash code. */ private int _keyHash; /** * Default constructor. */ protected Entry() { } /** * Returns the entry after this one. * * @return the next entry. */ public final Entry getNext() { return _next; } /** * Returns the entry before this one. * * @return the previous entry. */ public final Entry getPrevious() { return _previous; } /** * Returns the key for this entry. * * @return the entry key. */ public final K getKey() { return _key; } /** * Returns the value for this entry. * * @return the entry value. */ public final V getValue() { return _value; } /** * Sets the value for this entry. * * @param value the new value. * @return the previous value. */ public final V setValue(V value) { V old = _value; _value = value; return old; } /** * Indicates if this entry is considered equals to the specified entry * (using default value and key equality comparator to ensure symetry). * * @param that the object to test for equality. * @return true if both entry have equal keys and values. * false otherwise. */ public boolean equals(Object that) { if (that instanceof Map.Entry) { Map.Entry entry = (Map.Entry) that; return FastComparator.DEFAULT.areEqual(_key, entry.getKey()) && FastComparator.DEFAULT.areEqual(_value, entry.getValue()); } else { return false; } } /** * Returns the hash code for this entry. * * @return this entry hash code. */ public int hashCode() { return ((_key != null) ? _key.hashCode() : 0) ^ ((_value != null) ? _value.hashCode() : 0); } // Implements abstract method. public String toString() { // We use Text concatenation instead of TextBuilder here to avoid // copying the text representation of the keys/values (unknown length) return _key + "=" + _value; } } /** * This class represents an read-only view over a {@link FastMap}. */ private final class Unmodifiable implements Map, Serializable { public boolean equals(Object obj) { return FastMap.this.equals(obj); } public int hashCode() { return FastMap.this.hashCode(); } public int size() { return FastMap.this.size(); } public boolean isEmpty() { return FastMap.this.isEmpty(); } public boolean containsKey(Object key) { return FastMap.this.containsKey(key); } public boolean containsValue(Object value) { return FastMap.this.containsValue(value); } public Object get(Object key) { return FastMap.this.get(key); } public Object put(Object key, Object value) { throw new UnsupportedOperationException("Unmodifiable map"); } public Object remove(Object key) { throw new UnsupportedOperationException("Unmodifiable map"); } public void putAll(Map map) { throw new UnsupportedOperationException("Unmodifiable map"); } public void clear() { throw new UnsupportedOperationException("Unmodifiable map"); } public Set keySet() { return (Set) ((KeySet) FastMap.this.keySet()).unmodifiable(); } public Collection values() { return ((Values) FastMap.this.values()).unmodifiable(); } public Set entrySet() { throw new UnsupportedOperationException( "Direct view over unmodifiable map entries is not supported " + " (to prevent access to Entry.setValue(Object) method). " + "To iterate over unmodifiable map entries, applications may " + "use the keySet() and values() fast collection views " + "in conjonction."); } } // Reset the specified table. private static void reset(Object[] entries) { for (int i = 0; i < entries.length;) { int count = Math.min(entries.length - i, C1); System.arraycopy(NULL_ENTRIES, 0, entries, i, count); i += count; } } private static final Entry[] NULL_ENTRIES = new Entry[C1]; static volatile int ONE_VOLATILE = 1; // To prevent reordering. private static final long serialVersionUID = 1L; }