com.carrotsearch.hppcrt.maps.LongObjectOpenCustomHashMap Maven / Gradle / Ivy
Show all versions of hppcrt Show documentation
package com.carrotsearch.hppcrt.maps;
import java.util.*;
import com.carrotsearch.hppcrt.*;
import com.carrotsearch.hppcrt.cursors.*;
import com.carrotsearch.hppcrt.predicates.*;
import com.carrotsearch.hppcrt.procedures.*;
import com.carrotsearch.hppcrt.strategies.*;
// If RH is defined, RobinHood Hashing is in effect :
/**
* A hash map of long
to VType
, implemented using open
* addressing with linear probing for collision resolution.
*
* The difference with {@link LongObjectOpenHashMap} is that it uses a
* {@link LongHashingStrategy} to compare objects externally instead of using
* the built-in hashCode() / equals(). In particular, the management of null
* keys is up to the {@link LongHashingStrategy} implementation.
* The internal buffers of this implementation ({@link #keys}), {@link #allocated})
* 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 LongHashingStrategy} to
* assure good performance.
*
* This implementation supports null
values.
*
* @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 = "2014-10-25T20:54:06+0200", value = "HPPC-RT generated from: LongObjectOpenCustomHashMap.java")
public class LongObjectOpenCustomHashMap
implements LongObjectMap, 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 VType defaultValue = null;
/**
* Hash-indexed array holding all keys.
*
* Direct map iteration: iterate {keys[i], values[i]} for i in [0; keys.length[ where this.allocated[i] is 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
* @see #allocated
*/
public long[] keys;
/**
* Hash-indexed array holding all values associated to the keys.
* stored in {@link #keys}.
*
* @see #keys
*/
public VType[] values;
/**
* Information if an entry (slot) in the {@link #keys} table is allocated
* or empty.
* * In addition it caches hash value : If = -1, it means not allocated, else = HASH(keys[i]) & mask
* for every index i.
* * @see #assigned
*/
public int[] allocated;
/**
* Cached number of assigned slots in {@link #allocated}.
*/
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 #allocated} 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 LongHashingStrategy 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 LongHashingStrategy}
*
*
See class notes about hash distribution importance.
*/
public LongObjectOpenCustomHashMap(final LongHashingStrategy hashStrategy)
{
this(LongObjectOpenCustomHashMap.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 LongHashingStrategy}
*
* @param initialCapacity Initial capacity (greater than zero and automatically
* rounded to the next power of two).
*/
public LongObjectOpenCustomHashMap(final int initialCapacity, final LongHashingStrategy hashStrategy)
{
this(initialCapacity, LongObjectOpenCustomHashMap.DEFAULT_LOAD_FACTOR, hashStrategy);
}
/**
* Creates a hash map with the given initial capacity,
* load factor, using the hashStrategy as {@link LongHashingStrategy}
*
* @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 LongObjectOpenCustomHashMap(final int initialCapacity, final float loadFactor, final LongHashingStrategy hashStrategy)
{
//only accept not-null strategies.
if (hashStrategy != null)
{
this.hashStrategy = hashStrategy;
}
else {
throw new IllegalArgumentException("LongObjectOpenCustomHashMap() 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) + LongObjectOpenCustomHashMap.MIN_CAPACITY;
//align on next power of two
internalCapacity = HashContainerUtils.roundCapacity(internalCapacity);
this.keys = new long [internalCapacity];
this.values = Internals.newArray(internalCapacity);
//fill with "not allocated" value
this.allocated = new int[internalCapacity];
Internals.blankIntArrayMinusOne(this.allocated, 0, this.allocated.length);
//Take advantage of the rounding so that the resize occur a bit later than expected.
//allocate so that there is at least one slot that remains allocated = false
//this is compulsory to guarantee proper stop in searching loops
this.resizeAt = Math.max(3, (int) (internalCapacity * loadFactor)) - 2;
}
/**
* Create a hash map from all key-value pairs of another container.
*/
public LongObjectOpenCustomHashMap(final LongObjectAssociativeContainer container, final LongHashingStrategy hashStrategy)
{
this(container.size(), hashStrategy);
putAll(container);
}
/**
* {@inheritDoc}
*/
@Override
public VType put(long key, VType value)
{
assert this.assigned < this.allocated.length;
final int mask = this.allocated.length - 1;
final LongHashingStrategy strategy = this.hashStrategy;
int slot = Internals.rehash(strategy.computeHashCode(key)) & mask;
final long[] keys = this.keys;
final VType[] values = this.values;
final int[] allocated = this.allocated;
long tmpKey;
VType tmpValue;
int tmpAllocated;
int initial_slot = slot;
int dist = 0;
int existing_distance = 0;
while (allocated[slot] != -1 )
{
if (strategy.equals(key, keys[slot]))
{
final VType oldValue = values[slot];
values[slot] = value;
return oldValue;
}
//re-shuffle keys to minimize variance
existing_distance = (slot < allocated[slot] ? slot + allocated.length - allocated[slot] : slot - allocated[slot]);
if (dist > existing_distance)
{
//swap current (key, value, initial_slot) with slot places
tmpKey = keys[slot];
keys[slot] = key;
key = tmpKey;
tmpAllocated = allocated[slot];
allocated[slot] = initial_slot;
initial_slot = tmpAllocated;
tmpValue = values[slot];
values[slot] = value;
value = tmpValue;
dist = existing_distance;
}
slot = (slot + 1) & mask;
dist++;
}
// Check if we need to grow. If so, reallocate new data, fill in the last element
// and rehash.
if (this.assigned == this.resizeAt)
{
expandAndPut(key, value, slot);
}
else
{
this.assigned++;
allocated[slot] = initial_slot;
keys[slot] = key;
values[slot] = value;
}
return this.defaultValue;
}
/**
* {@inheritDoc}
*/
@Override
public int putAll(final LongObjectAssociativeContainer extends VType> container)
{
return putAll((Iterable extends LongObjectCursor extends VType>>) container);
}
/**
* {@inheritDoc}
*/
@Override
public int putAll(final Iterable extends LongObjectCursor extends VType>> iterable)
{
final int count = this.assigned;
for (final LongObjectCursor extends VType> c : iterable)
{
put(c.key, c.value);
}
return this.assigned - count;
}
/**
* {@inheritDoc}
*/
@Override
public boolean putIfAbsent(final long key, final VType value)
{
if (!containsKey(key))
{
put(key, value);
return true;
}
return false;
}
/**
* Expand the internal storage buffers (capacity) and rehash.
*/
private void expandAndPut(final long pendingKey, final VType pendingValue, final int freeSlot)
{
assert this.assigned == this.resizeAt;
assert this.allocated[freeSlot] == -1;
// Try to allocate new buffers first. If we OOM, it'll be now without
// leaving the data structure in an inconsistent state.
final long[] oldKeys = this.keys;
final VType[] oldValues = this.values;
final int[] oldAllocated = this.allocated;
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++;
//We don't care of the oldAllocated value, so long it means "allocated = true", since the whole set is rebuilt from scratch.
oldAllocated[freeSlot] = 1;
oldKeys[freeSlot] = pendingKey;
oldValues[freeSlot] = pendingValue;
//for inserts
final int mask = this.allocated.length - 1;
final LongHashingStrategy strategy = this.hashStrategy;
long key = (0L);
VType value = null;
int slot = -1;
final long[] keys = this.keys;
final VType[] values = this.values;
final int[] allocated = this.allocated;
long tmpKey = (0L);
VType tmpValue = null;
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 = oldAllocated.length; --i >= 0;)
{
if (oldAllocated[i] != -1 )
{
key = oldKeys[i];
value = oldValues[i];
slot = Internals.rehash(strategy.computeHashCode(key)) & mask;
initial_slot = slot;
dist = 0;
while (allocated[slot] != -1 )
{
//re-shuffle keys to minimize variance
existing_distance = (slot < allocated[slot] ? slot + allocated.length - allocated[slot] : slot - allocated[slot]);
if (dist > existing_distance)
{
//swap current (key, value, initial_slot) with slot places
tmpKey = keys[slot];
keys[slot] = key;
key = tmpKey;
tmpAllocated = allocated[slot];
allocated[slot] = initial_slot;
initial_slot = tmpAllocated;
tmpValue = values[slot];
values[slot] = value;
value = tmpValue;
dist = existing_distance;
}
slot = (slot + 1) & mask;
dist++;
} //end while
allocated[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 long[] keys = new long [capacity];
final VType[] values = Internals.newArray(capacity);
final int[] allocated = new int[capacity];
Internals.blankIntArrayMinusOne(allocated, 0, allocated.length);
this.keys = keys;
this.values = values;
this.allocated = allocated;
//allocate so that there is at least one slot that remains allocated = false
//this is compulsory to guarantee proper stop in searching loops
this.resizeAt = Math.max(3, (int) (capacity * this.loadFactor)) - 2;
}
/**
* {@inheritDoc}
*/
@Override
public VType remove(final long key)
{
final int mask = this.allocated.length - 1;
final LongHashingStrategy strategy = this.hashStrategy;
int slot = Internals.rehash(strategy.computeHashCode(key)) & mask;
int dist = 0;
final long[] keys = this.keys;
final int[] states = this.allocated;
while (states[slot] != -1
&& dist <= (slot < states[slot] ? slot + states.length - states[slot] : slot - states[slot]) )
{
if (strategy.equals(key, keys[slot]))
{
final VType value = this.values[slot];
this.assigned--;
shiftConflictingKeys(slot);
return value;
}
slot = (slot + 1) & mask;
dist++;
} //end while true
return this.defaultValue;
}
/**
* Shift all the slot-conflicting keys allocated to (and including) slot
.
*/
protected void shiftConflictingKeys(int slotCurr)
{
// Copied nearly verbatim from fastutil's impl.
final int mask = this.allocated.length - 1;
int slotPrev, slotOther;
final LongHashingStrategy strategy = this.hashStrategy;
final long[] keys = this.keys;
final VType[] values = this.values;
final int[] allocated = this.allocated;
while (true)
{
slotCurr = ((slotPrev = slotCurr) + 1) & mask;
while (allocated[slotCurr] != -1 )
{
//use the cached value, no need to recompute
slotOther = allocated[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 (
allocated[slotCurr] == -1
)
{
break;
}
// Shift key/value/allocated triplet.
keys[slotPrev] = keys[slotCurr];
values[slotPrev] = values[slotCurr];
allocated[slotPrev] = allocated[slotCurr];
}
//means not allocated
allocated[slotPrev] = -1;
/* */
/* */
values[slotPrev] = null;
/* */
}
/**
* {@inheritDoc}
*/
@Override
public int removeAll(final LongContainer container)
{
final int before = this.assigned;
for (final LongCursor cursor : container)
{
remove(cursor.value);
}
return before - this.assigned;
}
/**
* {@inheritDoc}
* Important!
* If the predicate actually injects the removed keys in another hash container, you may experience performance losses.
*/
@Override
public int removeAll(final LongPredicate predicate)
{
final int before = this.assigned;
final long[] keys = this.keys;
final int[] states = this.allocated;
for (int i = 0; i < states.length;)
{
if (states[i] != -1 )
{
if (predicate.apply(keys[i]))
{
this.assigned--;
shiftConflictingKeys(i);
// Repeat the check for the same i.
continue;
}
}
i++;
}
return before - this.assigned;
}
/**
* {@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 VType get(final long key)
{
final int mask = this.allocated.length - 1;
final LongHashingStrategy strategy = this.hashStrategy;
int slot = Internals.rehash(strategy.computeHashCode(key)) & mask;
int dist = 0;
final long[] keys = this.keys;
final int[] states = this.allocated;
while (states[slot] != -1
&& dist <= (slot < states[slot] ? slot + states.length - states[slot] : slot - states[slot]) )
{
if (strategy.equals(key, keys[slot]))
{
return this.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 long lkey()
{
assert this.lastSlot >= 0 : "Call containsKey() first.";
assert this.allocated[this.lastSlot] != -1 : "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 VType lget()
{
assert this.lastSlot >= 0 : "Call containsKey() first.";
assert this.allocated[this.lastSlot] != -1 : "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 VType lset(final VType key)
{
assert this.lastSlot >= 0 : "Call containsKey() first.";
assert this.allocated[this.lastSlot] != -1 : "Last call to exists did not have any associated value.";
final VType previous = this.values[this.lastSlot];
this.values[this.lastSlot] = key;
return previous;
}
/**
* @return Returns the slot of the last key looked up in a call to {@link #containsKey} if
* it returned true
.
*
* @see #containsKey
*/
public int lslot()
{
assert this.lastSlot >= 0 : "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);
*
* */
@Override
public boolean containsKey(final long key)
{
final int mask = this.allocated.length - 1;
final LongHashingStrategy strategy = this.hashStrategy;
int slot = Internals.rehash(strategy.computeHashCode(key)) & mask;
int dist = 0;
final long[] keys = this.keys;
final int[] states = this.allocated;
while (states[slot] != -1
&& dist <= (slot < states[slot] ? slot + states.length - states[slot] : slot - states[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.
Internals.blankIntArrayMinusOne(this.allocated, 0, this.allocated.length);
//Faster than Arrays.fill(values, null); // Help the GC.
Internals.blankObjectArray(this.values, 0, this.values.length);
}
/**
* {@inheritDoc}
*/
@Override
public int size()
{
return this.assigned;
}
/**
* {@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()
{
int h = 0;
final long[] keys = this.keys;
final VType[] values = this.values;
final int[] states = this.allocated;
final LongHashingStrategy strategy = this.hashStrategy;
for (int i = states.length; --i >= 0;)
{
if (states[i] != -1 )
{
//This hash is an intrinsic property of the container contents,
//consequently is independent from the HashStrategy, so do not use it !
h += Internals.rehash(strategy.computeHashCode(keys[i])) + Internals.rehash(values[i]);
}
}
return h;
}
/**
* this instance and obj can only be equal if :
* (both are LongObjectOpenCustomHashMap)
* and
* (both have equal hash strategies defined by {@link #LongHashingStrategy}.equals(obj.hashStrategy))
* then, both maps are compared using their {@link #LongHashingStrategy}.
*/
@Override
public boolean equals(final Object obj)
{
if (obj != null)
{
if (obj == this)
{
return true;
}
if (!(obj instanceof LongObjectOpenCustomHashMap))
{
return false;
}
if (!this.hashStrategy.equals(((LongObjectOpenCustomHashMap) obj).hashStrategy))
{
return false;
}
@SuppressWarnings("unchecked")
final LongObjectOpenCustomHashMap other = (LongObjectOpenCustomHashMap) obj;
if (other.size() == this.size())
{
final EntryIterator it = this.iterator();
while (it.hasNext())
{
final LongObjectCursor c = it.next();
if (other.containsKey(c.key))
{
final VType v = other.get(c.key);
if (((c.value) == null ? (v) == null : (c.value).equals((v))))
{
continue;
}
}
//recycle
it.release();
return false;
}
return true;
}
}
return false;
}
/**
* An iterator implementation for {@link #iterator}.
*/
public final class EntryIterator extends AbstractIterator>
{
public final LongObjectCursor cursor;
public EntryIterator()
{
this.cursor = new LongObjectCursor();
this.cursor.index = -2;
}
/**
* Iterate backwards w.r.t the buffer, to
* minimize collision chains when filling another hash container (ex. with putAll())
*/
@Override
protected LongObjectCursor fetch()
{
int i = this.cursor.index - 1;
while (i >= 0 &&
LongObjectOpenCustomHashMap.this.allocated[i] == -1
)
{
i--;
}
if (i == -1)
{
return done();
}
this.cursor.index = i;
this.cursor.key = LongObjectOpenCustomHashMap.this.keys[i];
this.cursor.value = LongObjectOpenCustomHashMap.this.values[i];
return this.cursor;
}
}
/**
* internal pool of EntryIterator
*/
protected final IteratorPool, EntryIterator> entryIteratorPool = new IteratorPool, EntryIterator>(
new ObjectFactory() {
@Override
public EntryIterator create()
{
return new EntryIterator();
}
@Override
public void initialize(final EntryIterator obj)
{
obj.cursor.index = LongObjectOpenCustomHashMap.this.keys.length;
}
@Override
public void reset(final EntryIterator obj) {
// nothing
}
});
/**
* {@inheritDoc}
* @return
*/
@Override
public EntryIterator iterator()
{
//return new EntryIterator();
return this.entryIteratorPool.borrow();
}
/**
* {@inheritDoc}
*/
@Override
public > T forEach(final T procedure)
{
final long[] keys = this.keys;
final VType[] values = this.values;
final int[] states = this.allocated;
//Iterate in reverse for side-stepping the longest conflict chain
//in another hash, in case apply() is actually used to fill another hash container.
for (int i = states.length - 1; i >= 0; i--)
{
if (states[i] != -1 )
{
procedure.apply(keys[i], values[i]);
}
}
return procedure;
}
/**
* {@inheritDoc}
*/
@Override
public > T forEach(final T predicate)
{
final long[] keys = this.keys;
final VType[] values = this.values;
final int[] states = this.allocated;
//Iterate in reverse for side-stepping the longest conflict chain
//in another hash, in case apply() is actually used to fill another hash container.
for (int i = states.length - 1; i >= 0; i--)
{
if (states[i] != -1 )
{
if (!predicate.apply(keys[i], values[i]))
{
break;
}
}
} //end for
return predicate;
}
/**
* @return a new KeysContainer view of the keys of this associated container.
* This view then reflects all changes from the map.
*/
@Override
public KeysContainer keys()
{
return new KeysContainer();
}
/**
* A view of the keys inside this hash map.
*/
public final class KeysContainer
extends AbstractLongCollection implements LongLookupContainer
{
private final LongObjectOpenCustomHashMap owner =
LongObjectOpenCustomHashMap.this;
@Override
public boolean contains(final long e)
{
return containsKey(e);
}
@Override
public T forEach(final T procedure)
{
final long[] keys = this.owner.keys;
final int[] states = this.owner.allocated;
//Iterate in reverse for side-stepping the longest conflict chain
//in another hash, in case apply() is actually used to fill another hash container.
for (int i = states.length - 1; i >= 0; i--)
{
if (states[i] != -1 )
{
procedure.apply(keys[i]);
}
}
return procedure;
}
@Override
public T forEach(final T predicate)
{
final long[] keys = this.owner.keys;
final int[] states = this.owner.allocated;
//Iterate in reverse for side-stepping the longest conflict chain
//in another hash, in case apply() is actually used to fill another hash container.
for (int i = states.length - 1; i >= 0; i--)
{
if (states[i] != -1 )
{
if (!predicate.apply(keys[i]))
{
break;
}
}
}
return predicate;
}
/**
* {@inheritDoc}
*/
@Override
public KeysIterator iterator()
{
//return new KeysIterator();
return this.keyIteratorPool.borrow();
}
/**
* {@inheritDoc}
*/
@Override
public int size()
{
return this.owner.size();
}
/**
* {@inheritDoc}
*/
@Override
public int capacity() {
return this.owner.capacity();
}
@Override
public void clear()
{
this.owner.clear();
}
@Override
public int removeAll(final LongPredicate predicate)
{
return this.owner.removeAll(predicate);
}
@Override
public int removeAllOccurrences(final long e)
{
final boolean hasKey = this.owner.containsKey(e);
int result = 0;
if (hasKey)
{
this.owner.remove(e);
result = 1;
}
return result;
}
/**
* internal pool of KeysIterator
*/
protected final IteratorPool keyIteratorPool = new IteratorPool(
new ObjectFactory() {
@Override
public KeysIterator create()
{
return new KeysIterator();
}
@Override
public void initialize(final KeysIterator obj)
{
obj.cursor.index = LongObjectOpenCustomHashMap.this.keys.length;
}
@Override
public void reset(final KeysIterator obj) {
// nothing
}
});
@Override
public long[] toArray(final long[] target)
{
final long[] keys = this.owner.keys;
final int[] states = this.owner.allocated;
int count = 0;
for (int i = 0; i < keys.length; i++)
{
if (states[i] != -1 )
{
target[count++] = keys[i];
}
}
assert count == this.owner.assigned;
return target;
}
};
/**
* An iterator over the set of assigned keys.
*/
public final class KeysIterator extends AbstractIterator
{
public final LongCursor cursor;
public KeysIterator()
{
this.cursor = new LongCursor();
this.cursor.index = -2;
}
/**
* Iterate backwards w.r.t the buffer, to
* minimize collision chains when filling another hash container (ex. with putAll())
*/
@Override
protected LongCursor fetch()
{
int i = this.cursor.index - 1;
while (i >= 0 &&
LongObjectOpenCustomHashMap.this.allocated[i] == -1
)
{
i--;
}
if (i == -1)
{
return done();
}
this.cursor.index = i;
this.cursor.value = LongObjectOpenCustomHashMap.this.keys[i];
return this.cursor;
}
}
/**
* @return a new ValuesContainer, view of the values of this map.
* This view then reflects all changes from the map.
*/
@Override
public ValuesContainer values()
{
return new ValuesContainer();
}
/**
* A view over the set of values of this map.
*/
public final class ValuesContainer extends AbstractObjectCollection
{
private final LongObjectOpenCustomHashMap owner =
LongObjectOpenCustomHashMap.this;
/**
* {@inheritDoc}
*/
@Override
public int size()
{
return this.owner.size();
}
/**
* {@inheritDoc}
*/
@Override
public int capacity() {
return this.owner.capacity();
}
@Override
public boolean contains(final VType value)
{
// This is a linear scan over the values, but it's in the contract, so be it.
final int[] states = this.owner.allocated;
final VType[] values = this.owner.values;
for (int slot = 0; slot < states.length; slot++)
{
if (
states[slot] != -1
&& ((value) == null ? (values[slot]) == null : (value).equals((values[slot]))))
{
return true;
}
}
return false;
}
@Override
public > T forEach(final T procedure)
{
final int[] states = this.owner.allocated;
final VType[] values = this.owner.values;
for (int slot = 0; slot < states.length; slot++)
{
if (
states[slot] != -1
) {
procedure.apply(values[slot]);
}
}
return procedure;
}
@Override
public > T forEach(final T predicate)
{
final int[] states = this.owner.allocated;
final VType[] values = this.owner.values;
for (int slot = 0; slot < states.length; slot++)
{
if (
states[slot] != -1
)
{
if (!predicate.apply(values[slot]))
{
break;
}
}
}
return predicate;
}
@Override
public ValuesIterator iterator()
{
// return new ValuesIterator();
return this.valuesIteratorPool.borrow();
}
/**
* {@inheritDoc}
* Indeed removes all the (key,value) pairs matching
* (key ? , e) with the same e, from the map.
*/
@Override
public int removeAllOccurrences(final VType e)
{
final int before = this.owner.assigned;
final VType[] values = this.owner.values;
final int[] states = this.owner.allocated;
for (int i = 0; i < states.length;)
{
if (states[i] != -1 )
{
if (((e) == null ? (values[i]) == null : (e).equals((values[i]))))
{
this.owner.assigned--;
shiftConflictingKeys(i);
// Repeat the check for the same i.
continue;
}
}
i++;
}
return before - this.owner.assigned;
}
/**
* {@inheritDoc}
* Indeed removes all the (key,value) pairs matching
* the predicate for the values, from the map.
*/
@Override
public int removeAll(final ObjectPredicate super VType> predicate)
{
final int before = this.owner.assigned;
final VType[] values = this.owner.values;
final int[] states = this.owner.allocated;
for (int i = 0; i < states.length;)
{
if (states[i] != -1 )
{
if (predicate.apply(values[i]))
{
this.owner.assigned--;
shiftConflictingKeys(i);
// Repeat the check for the same i.
continue;
}
}
i++;
}
return before - this.owner.assigned;
}
/**
* {@inheritDoc}
* Alias for clear() the whole map.
*/
@Override
public void clear()
{
this.owner.clear();
}
/**
* internal pool of ValuesIterator
*/
protected final IteratorPool, ValuesIterator> valuesIteratorPool = new IteratorPool, ValuesIterator>(
new ObjectFactory() {
@Override
public ValuesIterator create()
{
return new ValuesIterator();
}
@Override
public void initialize(final ValuesIterator obj)
{
obj.cursor.index = LongObjectOpenCustomHashMap.this.keys.length;
}
@Override
public void reset(final ValuesIterator obj) {
// nothing
}
});
@Override
public VType[] toArray(final VType[] target)
{
final int[] states = this.owner.allocated;
final VType[] values = this.owner.values;
int count = 0;
for (int i = 0; i < values.length; i++)
{
if (states[i] != -1 )
{
target[count++] = values[i];
}
}
assert count == this.owner.assigned;
return target;
}
}
/**
* An iterator over the set of assigned values.
*/
public final class ValuesIterator extends AbstractIterator>
{
public final ObjectCursor cursor;
public ValuesIterator()
{
this.cursor = new ObjectCursor();
this.cursor.index = -2;
}
/**
* Iterate backwards w.r.t the buffer, to
* minimize collision chains when filling another hash container (ex. with putAll())
*/
@Override
protected ObjectCursor fetch()
{
int i = this.cursor.index - 1;
while (i >= 0 &&
LongObjectOpenCustomHashMap.this.allocated[i] == -1
)
{
i--;
}
if (i == -1)
{
return done();
}
this.cursor.index = i;
this.cursor.value = LongObjectOpenCustomHashMap.this.values[i];
return this.cursor;
}
}
/**
* Clone this object.
* * The returned clone will use the same HashingStrategy strategy.
* It also realizes a trim-to- this.size() in the process.
* */
@Override
public LongObjectOpenCustomHashMap clone()
{
/* */
@SuppressWarnings("unchecked")
final/* */
LongObjectOpenCustomHashMap cloned =
new LongObjectOpenCustomHashMap(this.size(), this.loadFactor, this.hashStrategy);
cloned.putAll(this);
cloned.defaultValue = this.defaultValue;
return cloned;
}
/**
* Convert the contents of this map to a human-friendly string.
*/
@Override
public String toString()
{
final StringBuilder buffer = new StringBuilder();
buffer.append("[");
boolean first = true;
for (final LongObjectCursor cursor : this)
{
if (!first)
{
buffer.append(", ");
}
buffer.append(cursor.key);
buffer.append("=>");
buffer.append(cursor.value);
first = false;
}
buffer.append("]");
return buffer.toString();
}
/**
* Creates a hash map from two index-aligned arrays of key-value pairs.
*/
public static LongObjectOpenCustomHashMap from(final long[] keys, final VType[] values, final LongHashingStrategy hashStrategy)
{
if (keys.length != values.length)
{
throw new IllegalArgumentException("Arrays of keys and values must have an identical length.");
}
final LongObjectOpenCustomHashMap map = new LongObjectOpenCustomHashMap(keys.length, hashStrategy);
for (int i = 0; i < keys.length; i++)
{
map.put(keys[i], values[i]);
}
return map;
}
/**
* Create a hash map from another associative container.
*/
public static LongObjectOpenCustomHashMap from(final LongObjectAssociativeContainer container, final LongHashingStrategy hashStrategy)
{
return new LongObjectOpenCustomHashMap(container, hashStrategy);
}
/**
* Create a new hash map without providing the full generic signature (constructor
* shortcut).
*/
public static LongObjectOpenCustomHashMap newInstance(final LongHashingStrategy hashStrategy)
{
return new LongObjectOpenCustomHashMap(hashStrategy);
}
/**
* Create a new hash map with initial capacity and load factor control. (constructor
* shortcut).
*/
public static LongObjectOpenCustomHashMap newInstance(final int initialCapacity, final float loadFactor, final LongHashingStrategy hashStrategy)
{
return new LongObjectOpenCustomHashMap(initialCapacity, loadFactor, hashStrategy);
}
/**
* Return the current {@link LongHashingStrategy} in use.
*/
public LongHashingStrategy strategy()
{
return this.hashStrategy;
}
/**
* Returns the "default value" value used
* in containers methods returning "default value"
* @return
*/
public VType getDefaultValue()
{
return this.defaultValue;
}
/**
* Set the "default value" value to be used
* in containers methods returning "default value"
* @return
*/
public void setDefaultValue(final VType defaultValue)
{
this.defaultValue = defaultValue;
}
}