com.carrotsearch.hppcrt.maps.ObjectLongOpenCustomHashMap Maven / Gradle / Ivy
Show all versions of hppcrt Show documentation
package com.carrotsearch.hppcrt.maps;
import com.carrotsearch.hppcrt.*;
import com.carrotsearch.hppcrt.cursors.*;
import com.carrotsearch.hppcrt.predicates.*;
import com.carrotsearch.hppcrt.procedures.*;
import com.carrotsearch.hppcrt.strategies.*;
import com.carrotsearch.hppcrt.hash.*;
//If RH is defined, RobinHood Hashing is in effect :
/**
* A hash map of KType
to long
, implemented using open
* addressing with linear probing for collision resolution.
*
* The difference with {@link ObjectLongOpenHashMap} is that it uses a
* {@link ObjectHashingStrategy} to compare objects externally instead of using
* the built-in hashCode() / equals(). In particular, the management of null
* keys is up to the {@link ObjectHashingStrategy} implementation.
* The internal buffers of this implementation ({@link #keys}, {@link #values})
* are always allocated to the nearest size that is a power of two. When
* the capacity exceeds the given load factor, the buffer size is doubled.
*
* Important note. The implementation uses power-of-two tables and linear
* probing, which may cause poor performance (many collisions) if hash values are
* not properly distributed. Therefore, it is up to the {@link ObjectHashingStrategy} to
* assure good performance.
*
* This implementation support null
keys. In addition, objects passed to the {@link ObjectHashingStrategy} are guaranteed to be not-null
.
*
* @author This code is inspired by the collaboration and implementation in the fastutil project.
*
* Robin-Hood hashing algorithm is also used to minimize variance
* in insertion and search-related operations, for an all-around smother operation at the cost
* of smaller peak performance:
* - Pedro Celis (1986) for the original Robin-Hood hashing paper,
* - MoonPolySoft/Cliff Moon for the initial Robin-hood on HPPC implementation,
* - Vincent Sonnier for the present implementation using cached hashes.
*
*/
@javax.annotation.Generated(date = "2015-02-27T19:21:05+0100", value = "HPPC-RT generated from: ObjectLongOpenCustomHashMap.java")
public class ObjectLongOpenCustomHashMap
implements ObjectLongMap, Cloneable
{
/**
* Minimum capacity for the map.
*/
public final static int MIN_CAPACITY = HashContainerUtils.MIN_CAPACITY;
/**
* Default capacity.
*/
public final static int DEFAULT_CAPACITY = HashContainerUtils.DEFAULT_CAPACITY;
/**
* Default load factor.
*/
public final static float DEFAULT_LOAD_FACTOR = HashContainerUtils.DEFAULT_LOAD_FACTOR;
protected long defaultValue = (0L);
/**
* Hash-indexed array holding all keys.
* Important!
* The actual value in this field is always an instance of Object[]
.
* Be warned that javac
emits additional casts when keys
* are directly accessed; these casts
* may result in exceptions at runtime. A workaround is to cast directly to
* Object[]
before accessing the buffer's elements (although it is highly
* recommended to use a {@link #iterator()} instead.
*
* * Direct map iteration: iterate {keys[i], values[i]} for i in [0; keys.length[ where keys[i] != 0/null, then also * {0/null, {@link #allocatedDefaultKeyValue} } is in the map if {@link #allocatedDefaultKey} = true. *
* *Direct iteration warning: * If the iteration goal is to fill another hash container, please iterate {@link #keys} in reverse to prevent performance losses. * @see #values */ public KType[] keys; /** * Hash-indexed array holding all values associated to the keys. * stored in {@link #keys}. *
Important!
* The actual value in this field is always an instance of Object[]
.
* Be warned that javac
emits additional casts when values
* are directly accessed; these casts
* may result in exceptions at runtime. A workaround is to cast directly to
* Object[]
before accessing the buffer's elements (although it is highly
* recommended to use a {@link #iterator()} instead.
*
*
* @see #keys
*/
public long[] values;
/**
* * Caches the hash value = HASH(keys[i]) & mask, if keys[i] != 0/null,
* for every index i.
* * @see #assigned
*/
protected int[] hash_cache;
/**
*True if key = 0/null is in the map.
*/
public boolean allocatedDefaultKey = false;
/**
* if allocatedDefaultKey = true, contains the associated V to the key = 0/null
*/
public long allocatedDefaultKeyValue;
/**
* Cached number of assigned slots in {@link #keys}.
*/
protected int assigned;
/**
* The load factor for this map (fraction of allocated slots
* before the buffers must be rehashed or reallocated).
*/
protected final float loadFactor;
/**
* Resize buffers when {@link #keys} hits this value.
*/
protected int resizeAt;
/**
* The most recent slot accessed in {@link #containsKey} (required for
* {@link #lget}).
*
* @see #containsKey
* @see #lget
*/
protected int lastSlot;
/**
* Custom hashing strategy :
* comparisons and hash codes of keys will be computed
* with the strategy methods instead of the native Object equals() and hashCode() methods.
*/
protected final ObjectHashingStrategy super KType> hashStrategy;
/**
* Creates a hash map with the default capacity of {@value #DEFAULT_CAPACITY},
* load factor of {@value #DEFAULT_LOAD_FACTOR}, using the hashStrategy as {@link ObjectHashingStrategy}
*
*
See class notes about hash distribution importance.
*/ public ObjectLongOpenCustomHashMap(final ObjectHashingStrategy super KType> hashStrategy) { this(ObjectLongOpenCustomHashMap.DEFAULT_CAPACITY, hashStrategy); } /** * Creates a hash map with the given initial capacity, default load factor of * {@value #DEFAULT_LOAD_FACTOR}, using the hashStrategy as {@link ObjectHashingStrategy} * * @param initialCapacity Initial capacity (greater than zero and automatically * rounded to the next power of two). */ public ObjectLongOpenCustomHashMap(final int initialCapacity, final ObjectHashingStrategy super KType> hashStrategy) { this(initialCapacity, ObjectLongOpenCustomHashMap.DEFAULT_LOAD_FACTOR, hashStrategy); } /** * Creates a hash map with the given initial capacity, * load factor, using the hashStrategy as {@link ObjectHashingStrategy} * * @param initialCapacity Initial capacity (greater than zero and automatically * rounded to the next power of two). * * @param loadFactor The load factor (greater than zero and smaller than 1). * * */ public ObjectLongOpenCustomHashMap(final int initialCapacity, final float loadFactor, final ObjectHashingStrategy super KType> hashStrategy) { //only accept not-null strategies. if (hashStrategy != null) { this.hashStrategy = hashStrategy; } else { throw new IllegalArgumentException("ObjectLongOpenCustomHashMap() cannot have a null hashStrategy !"); } assert loadFactor > 0 && loadFactor <= 1 : "Load factor must be between (0, 1]."; this.loadFactor = loadFactor; //take into account of the load factor to garantee no reallocations before reaching initialCapacity. int internalCapacity = (int) (initialCapacity / loadFactor) + ObjectLongOpenCustomHashMap.MIN_CAPACITY; //align on next power of two internalCapacity = HashContainerUtils.roundCapacity(internalCapacity); this.keys = (Internals.* if (containsKey(key)) * { * long v = (long) (lget() + additionValue); * lset(v); * return v; * } * else * { * put(key, putValue); * return putValue; * } ** * @param key The key of the value to adjust. * @param putValue The value to put if
key
does not exist.
* @param additionValue The value to add to the existing value if key
exists.
* @return Returns the current value associated with key
(after changes).
*/
@Override
public long putOrAdd(KType key, long putValue, long additionValue)
{
if ((null) == key) {
if (this.allocatedDefaultKey) {
this.allocatedDefaultKeyValue += additionValue;
return this.allocatedDefaultKeyValue;
}
this.allocatedDefaultKeyValue = putValue;
this.allocatedDefaultKey = true;
return putValue;
}
final int mask = this.keys.length - 1;
final KType[] keys = this.keys;
final long[] values = this.values;
final ObjectHashingStrategy super KType> strategy = this.hashStrategy;
long value = putValue;
//copied straight from fastutil "fast-path"
int slot;
KType curr;
//1.1 The rehashed key slot is occupied...
if ((curr = keys[slot = PhiMix.hash(strategy.computeHashCode(key)) & mask]) != (null)) {
//1.2 the occupied place is indeed key, so only increments the value and nothing else.
if (strategy.equals(curr, key)) {
values[slot] += additionValue;
return values[slot];
}
//1.3 key is colliding, manage below :
}
else if (this.assigned < this.resizeAt) {
//1.4 key is not colliding, without resize, so insert, return defaultValue.
keys[slot] = key;
values[slot] = value;
this.assigned++;
this.hash_cache[slot] = slot;
return putValue;
}
final int[] cached = this.hash_cache;
KType tmpKey;
long tmpValue;
int tmpAllocated;
int initial_slot = slot;
int dist = 0;
int existing_distance = 0;
while ((keys[slot] != (null)))
{
existing_distance = (slot < cached[slot] ? slot + cached.length - cached[slot] : slot - cached[slot]);
if (strategy.equals(key, keys[slot]))
{
values[slot] += additionValue;
return values[slot];
}
else if (dist > existing_distance)
{
//swap current (key, value, initial_slot) with slot places
tmpKey = keys[slot];
keys[slot] = key;
key = tmpKey;
tmpValue = values[slot];
values[slot] = value;
value = tmpValue;
tmpAllocated = cached[slot];
cached[slot] = initial_slot;
initial_slot = tmpAllocated;
dist = existing_distance;
}
slot = (slot + 1) & mask;
dist++;
}
if (assigned == resizeAt) {
expandAndPut(key, value, slot);
} else {
assigned++;
cached[slot] = initial_slot;
keys[slot] = key;
values[slot] = value;
}
return putValue;
}
/**
* An equivalent of calling
* * if (containsKey(key)) * { * long v = (long) (lget() + additionValue); * lset(v); * return v; * } * else * { * put(key, additionValue); * return additionValue; * } ** * @param key The key of the value to adjust. * @param additionValue The value to put or add to the existing value if
key
exists.
* @return Returns the current value associated with key
(after changes).
*/
@Override
public long addTo(KType key, long additionValue)
{
return putOrAdd(key, additionValue, additionValue);
}
/**
* Expand the internal storage buffers (capacity) and rehash.
*/
private void expandAndPut(final KType pendingKey, final long pendingValue, final int freeSlot)
{
assert this.assigned == this.resizeAt;
//default sentinel value is never in the keys[] array, so never trigger reallocs
assert pendingKey != (null);
// Try to allocate new buffers first. If we OOM, it'll be now without
// leaving the data structure in an inconsistent state.
final KType[] oldKeys = this.keys;
final long[] oldValues = this.values;
allocateBuffers(HashContainerUtils.nextCapacity(this.keys.length));
// We have succeeded at allocating new data so insert the pending key/value at
// the free slot in the old arrays before rehashing.
this.lastSlot = -1;
this.assigned++;
oldKeys[freeSlot] = pendingKey;
oldValues[freeSlot] = pendingValue;
//for inserts
final int mask = this.keys.length - 1;
final ObjectHashingStrategy super KType> strategy = this.hashStrategy;
KType key = (null);
long value = (0L);
int slot = -1;
final KType[] keys = this.keys;
final long[] values = this.values;
final int[] cached = this.hash_cache;
KType tmpKey = (null);
long tmpValue = (0L);
int tmpAllocated = -1;
int initial_slot = -1;
int dist = -1;
int existing_distance = -1;
//iterate all the old arrays to add in the newly allocated buffers
//It is important to iterate backwards to minimize the conflict chain length !
for (int i = oldKeys.length; --i >= 0;)
{
if ((oldKeys[i] != (null)))
{
key = oldKeys[i];
value = oldValues[i];
slot = PhiMix.hash(strategy.computeHashCode(key)) & mask;
initial_slot = slot;
dist = 0;
while ((keys[slot] != (null)))
{
//re-shuffle keys to minimize variance
existing_distance = (slot < cached[slot] ? slot + cached.length - cached[slot] : slot - cached[slot]);
if (dist > existing_distance)
{
//swap current (key, value, initial_slot) with slot places
tmpKey = keys[slot];
keys[slot] = key;
key = tmpKey;
tmpAllocated = cached[slot];
cached[slot] = initial_slot;
initial_slot = tmpAllocated;
tmpValue = values[slot];
values[slot] = value;
value = tmpValue;
dist = existing_distance;
}
slot = (slot + 1) & mask;
dist++;
} //end while
cached[slot] = initial_slot;
keys[slot] = key;
values[slot] = value;
}
}
}
/**
* Allocate internal buffers for a given capacity.
*
* @param capacity New capacity (must be a power of two).
*/
private void allocateBuffers(final int capacity)
{
final KType[] keys = (Internals.slot
.
*/
protected void shiftConflictingKeys(int slotCurr)
{
// Copied nearly verbatim from fastutil's impl.
final int mask = this.keys.length - 1;
int slotPrev, slotOther;
final ObjectHashingStrategy super KType> strategy = this.hashStrategy;
final KType[] keys = this.keys;
final long[] values = this.values;
final int[] cached = this.hash_cache;
while (true)
{
slotCurr = ((slotPrev = slotCurr) + 1) & mask;
while ((keys[slotCurr] != (null)))
{
//use the cached value, no need to recompute
slotOther = cached[slotCurr];
if (slotPrev <= slotCurr)
{
// we're on the right of the original slot.
if (slotPrev >= slotOther || slotOther > slotCurr)
{
break;
}
}
else
{
// we've wrapped around.
if (slotPrev >= slotOther && slotOther > slotCurr)
{
break;
}
}
slotCurr = (slotCurr + 1) & mask;
}
if (!(keys[slotCurr] != (null)))
{
break;
}
// Shift key/value/allocated triplet.
keys[slotPrev] = keys[slotCurr];
values[slotPrev] = values[slotCurr];
cached[slotPrev] = cached[slotCurr];
}
//means not allocated
keys[slotPrev] = (null);
/* */
}
/**
* {@inheritDoc}
*/
@Override
public int removeAll(final ObjectContainer extends KType> container)
{
final int before = this.size();
for (final ObjectCursor extends KType> cursor : container)
{
remove(cursor.value);
}
return before - this.size();
}
/**
* {@inheritDoc}
* Important! * If the predicate actually injects the removed keys in another hash container, you may experience performance losses. */ @Override public int removeAll(final ObjectPredicate super KType> predicate) { final int before = this.size(); if (this.allocatedDefaultKey) { if (predicate.apply((null))) { this.allocatedDefaultKey = false; } } final KType[] keys = this.keys; for (int i = 0; i < keys.length;) { if ((keys[i] != (null))) { if (predicate.apply(keys[i])) { this.assigned--; shiftConflictingKeys(i); // Repeat the check for the same i. continue; } } i++; } return before - this.size(); } /** * {@inheritDoc} * *
Use the following snippet of code to check for key existence * first and then retrieve the value if it exists.
** if (map.containsKey(key)) * value = map.lget(); **/ @Override public long get(final KType key) { if (key == (null)) { if (this.allocatedDefaultKey) { return this.allocatedDefaultKeyValue; } return this.defaultValue; } final int mask = this.keys.length - 1; final ObjectHashingStrategy super KType> strategy = this.hashStrategy; final KType[] keys = this.keys; final long[] values = this.values; //copied straight from fastutil "fast-path" int slot; KType curr; //1.1 The rehashed slot is free, nothing to get, return default value if ((curr = keys[slot = PhiMix.hash(strategy.computeHashCode(key)) & mask]) == (null)) { return this.defaultValue; } //1.2) The rehashed entry is occupied by the key, return value if (strategy.equals(curr, key)) { return values[slot]; } //2. Hash collision, search for the key along the path slot = (slot + 1) & mask; int dist = 0; final int[] cached = this.hash_cache; while ((keys[slot] != (null)) && dist <= (slot < cached[slot] ? slot + cached.length - cached[slot] : slot - cached[slot]) ) { if (strategy.equals(key, keys[slot])) { return values[slot]; } slot = (slot + 1) & mask; dist++; } //end while true return this.defaultValue; } /** * Returns the last key stored in this has map for the corresponding * most recent call to {@link #containsKey}. * Precondition : {@link #containsKey} must have been called previously ! *
Use the following snippet of code to check for key existence * first and then retrieve the key value if it exists.
** if (map.containsKey(key)) * value = map.lkey(); ** *
This is equivalent to calling:
** if (map.containsKey(key)) * key = map.keys[map.lslot()]; **/ public KType lkey() { if (this.lastSlot == -2) { return (null); } assert this.lastSlot >= 0 : "Call containsKey() first."; assert (this.keys[this.lastSlot] != (null)) : "Last call to exists did not have any associated value."; return this.keys[this.lastSlot]; } /** * Returns the last value saved in a call to {@link #containsKey}. * Precondition : {@link #containsKey} must have been called previously ! * @see #containsKey */ public long lget() { if (this.lastSlot == -2) { return this.allocatedDefaultKeyValue; } assert this.lastSlot >= 0 : "Call containsKey() first."; assert (this.keys[this.lastSlot] != (null)) : "Last call to exists did not have any associated value."; return this.values[this.lastSlot]; } /** * Sets the value corresponding to the key saved in the last * call to {@link #containsKey}, if and only if the key exists * in the map already. * Precondition : {@link #containsKey} must have been called previously ! * @see #containsKey * @return Returns the previous value stored under the given key. */ public long lset(final long value) { if (this.lastSlot == -2) { final long previous = this.allocatedDefaultKeyValue; this.allocatedDefaultKeyValue = value; return previous; } assert this.lastSlot >= 0 : "Call containsKey() first."; assert (this.keys[this.lastSlot] != (null)) : "Last call to exists did not have any associated value."; final long previous = this.values[this.lastSlot]; this.values[this.lastSlot] = value; return previous; } /** * @return Returns the slot of the last key looked up in a call to {@link #containsKey} if * it returned
true
* or else -2 if {@link #containsKey} were successful on key = 0
* @see #containsKey
*/
public int lslot()
{
assert this.lastSlot >= 0 || this.lastSlot == -2 : "Call containsKey() first.";
return this.lastSlot;
}
/**
* {@inheritDoc}
*
* Saves the associated value for fast access using {@link #lget} * or {@link #lset}.
** if (map.containsKey(key)) * value = map.lget(); ** or, to modify the value at the given key without looking up * its slot twice: *
* if (map.containsKey(key)) * map.lset(map.lget() + 1); ** or, to retrieve the key-equivalent object from the map: *
* if (map.containsKey(key)) * map.lkey(); **/ @Override public boolean containsKey(final KType key) { if (key == (null)) { if (this.allocatedDefaultKey) { this.lastSlot = -2; } else { this.lastSlot = -1; } return this.allocatedDefaultKey; } final int mask = this.keys.length - 1; final ObjectHashingStrategy super KType> strategy = this.hashStrategy; final KType[] keys = this.keys; //copied straight from fastutil "fast-path" int slot; KType curr; //1.1 The rehashed slot is free, return false if ((curr = keys[slot = PhiMix.hash(strategy.computeHashCode(key)) & mask]) == (null)) { this.lastSlot = -1; return false; } //1.2) The rehashed entry is occupied by the key, return true if (strategy.equals(curr, key)) { this.lastSlot = slot; return true; } //2. Hash collision, search for the key along the path slot = (slot + 1) & mask; int dist = 0; final int[] cached = this.hash_cache; while ((keys[slot] != (null)) && dist <= (slot < cached[slot] ? slot + cached.length - cached[slot] : slot - cached[slot]) ) { if (strategy.equals(key, keys[slot])) { this.lastSlot = slot; return true; } slot = (slot + 1) & mask; dist++; } //end while true //unsuccessful search this.lastSlot = -1; return false; } /** * {@inheritDoc} * *
Does not release internal buffers.
*/ @Override public void clear() { this.assigned = 0; this.lastSlot = -1; // States are always cleared. this.allocatedDefaultKey = false; //Faster than Arrays.fill(keys, null); // Help the GC. ObjectArrays.blankArray(this.keys, 0, this.keys.length); } /** * {@inheritDoc} */ @Override public int size() { return this.assigned + (this.allocatedDefaultKey ? 1 : 0); } /** * {@inheritDoc} */ @Override public int capacity() { return this.resizeAt - 1; } /** * {@inheritDoc} * *Note that an empty container may still contain many deleted keys (that occupy buffer * space). Adding even a single element to such a container may cause rehashing.
*/ @Override public boolean isEmpty() { return size() == 0; } /** * {@inheritDoc} */ @Override public int hashCode() { final ObjectHashingStrategy super KType> strategy = this.hashStrategy; int h = 0; if (this.allocatedDefaultKey) { h += 0 + Internals.rehash(this.allocatedDefaultKeyValue); } final KType[] keys = this.keys; final long[] values = this.values; for (int i = keys.length; --i >= 0;) { if ((keys[i] != (null))) { h += PhiMix.hash(strategy.computeHashCode(keys[i])) + Internals.rehash(values[i]); } } return h; } /** * {@inheritDoc} */ @Override public boolean equals(final Object obj) { if (obj != null) { if (obj == this) { return true; } if (!(obj instanceof ObjectLongOpenCustomHashMap)) { return false; } if (!this.hashStrategy.equals(((ObjectLongOpenCustomHashMap