javolution.util.stripped.FastMap Maven / Gradle / Ivy
Show all versions of sqlite4java Show documentation
/*
* 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 extends K, ? extends V> 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 extends K, ? extends V> 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 super K> 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 super K> getKeyComparator() {
return _keyComparator;
}
/**
* Sets the value comparator for this map.
*
* @param valueComparator the value comparator.
* @return this
*/
public FastMap setValueComparator(
FastComparator super V> valueComparator)
{
_valueComparator = valueComparator;
return this;
}
/**
* Returns the value comparator for this fast map.
*
* @return the value comparator.
*/
public FastComparator super V> 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;
}