org.agrona.collections.Long2ObjectHashMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Agrona Show documentation
Show all versions of Agrona Show documentation
High performance primitives and utility library.
/*
* Copyright 2014 - 2016 Real Logic Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.agrona.collections;
import org.agrona.BitUtil;
import org.agrona.generation.DoNotSub;
import java.util.*;
import java.util.function.LongFunction;
import static java.util.Objects.requireNonNull;
import static org.agrona.collections.CollectionUtil.validateLoadFactor;
/**
* {@link java.util.Map} implementation specialised for long keys using open addressing and
* linear probing for cache efficient access.
*
* @param type of values stored in the {@link java.util.Map}
*/
public class Long2ObjectHashMap
implements Map
{
private final float loadFactor;
@DoNotSub private int resizeThreshold;
@DoNotSub private int size;
private long[] keys;
private Object[] values;
private final ValueCollection valueCollection;
private final KeySet keySet;
private final EntrySet entrySet;
public Long2ObjectHashMap()
{
this(8, 0.67f);
}
/**
* Construct a new map allowing a configuration for initial capacity and load factor.
*
* @param initialCapacity for the backing array
* @param loadFactor limit for resizing on puts
*/
public Long2ObjectHashMap(
@DoNotSub final int initialCapacity,
final float loadFactor)
{
validateLoadFactor(loadFactor);
this.loadFactor = loadFactor;
/* @DoNotSub */ final int capacity = BitUtil.findNextPositivePowerOfTwo(initialCapacity);
/* @DoNotSub */ resizeThreshold = (int)(capacity * loadFactor);
keys = new long[capacity];
values = new Object[capacity];
// Cached to avoid allocation.
valueCollection = new ValueCollection<>();
keySet = new KeySet();
entrySet = new EntrySet<>();
}
/**
* Get the load factor beyond which the map will increase size.
*
* @return load factor for when the map should increase size.
*/
public float loadFactor()
{
return loadFactor;
}
/**
* Get the total capacity for the map to which the load factor with be a fraction of.
*
* @return the total capacity for the map.
*/
@DoNotSub public int capacity()
{
return values.length;
}
/**
* Get the actual threshold which when reached the map resize.
* This is a function of the current capacity and load factor.
*
* @return the threshold when the map will resize.
*/
@DoNotSub public int resizeThreshold()
{
return resizeThreshold;
}
/**
* {@inheritDoc}
*/
@DoNotSub public int size()
{
return size;
}
/**
* {@inheritDoc}
*/
public boolean isEmpty()
{
return 0 == size;
}
/**
* {@inheritDoc}
*/
public boolean containsKey(final Object key)
{
return containsKey(((Long)key).longValue());
}
/**
* Overloaded version of {@link Map#containsKey(Object)} that takes a primitive long key.
*
* @param key for indexing the {@link Map}
* @return true if the key is found otherwise false.
*/
public boolean containsKey(final long key)
{
@DoNotSub final int mask = values.length - 1;
@DoNotSub int index = Hashing.hash(key, mask);
boolean found = false;
while (null != values[index])
{
if (key == keys[index])
{
found = true;
break;
}
index = ++index & mask;
}
return found;
}
/**
* {@inheritDoc}
*/
public boolean containsValue(final Object value)
{
boolean found = false;
if (null != value)
{
for (final Object v : values)
{
if (value.equals(v))
{
found = true;
break;
}
}
}
return found;
}
/**
* {@inheritDoc}
*/
public V get(final Object key)
{
return get(((Long)key).longValue());
}
/**
* Overloaded version of {@link Map#get(Object)} that takes a primitive long key.
*
* @param key for indexing the {@link Map}
* @return the value if found otherwise null
*/
@SuppressWarnings("unchecked")
public V get(final long key)
{
@DoNotSub final int mask = values.length - 1;
@DoNotSub int index = Hashing.hash(key, mask);
Object value;
while (null != (value = values[index]))
{
if (key == keys[index])
{
break;
}
index = ++index & mask;
}
return (V)value;
}
/**
* Get a value for a given key, or if it does ot exist then default the value via a {@link java.util.function.LongFunction}
* and put it in the map.
*
* Primitive specialized version of {@link java.util.Map#computeIfAbsent}.
*
* @param key to search on.
* @param mappingFunction to provide a value if the get returns null.
* @return the value if found otherwise the default.
*/
public V computeIfAbsent(final long key, final LongFunction mappingFunction)
{
requireNonNull(mappingFunction, "mappingFunction cannot be null");
V value = get(key);
if (value == null)
{
value = mappingFunction.apply(key);
if (value != null)
{
put(key, value);
}
}
return value;
}
/**
* {@inheritDoc}
*/
public V put(final Long key, final V value)
{
return put(key.longValue(), value);
}
/**
* Overloaded version of {@link Map#put(Object, Object)} that takes a primitive long key.
*
* @param key for indexing the {@link Map}
* @param value to be inserted in the {@link Map}
* @return the previous value if found otherwise null
*/
@SuppressWarnings("unchecked")
public V put(final long key, final V value)
{
requireNonNull(value, "Value cannot be null");
V oldValue = null;
@DoNotSub final int mask = values.length - 1;
@DoNotSub int index = Hashing.hash(key, mask);
while (null != values[index])
{
if (key == keys[index])
{
oldValue = (V)values[index];
break;
}
index = ++index & mask;
}
if (null == oldValue)
{
++size;
keys[index] = key;
}
values[index] = value;
if (size > resizeThreshold)
{
increaseCapacity();
}
return oldValue;
}
/**
* {@inheritDoc}
*/
public V remove(final Object key)
{
return remove(((Long)key).longValue());
}
/**
* Overloaded version of {@link Map#remove(Object)} that takes a primitive long key.
*
* @param key for indexing the {@link Map}
* @return the value if found otherwise null
*/
@SuppressWarnings("unchecked")
public V remove(final long key)
{
@DoNotSub final int mask = values.length - 1;
@DoNotSub int index = Hashing.hash(key, mask);
Object value;
while (null != (value = values[index]))
{
if (key == keys[index])
{
values[index] = null;
--size;
compactChain(index);
break;
}
index = ++index & mask;
}
return (V)value;
}
/**
* {@inheritDoc}
*/
public void clear()
{
size = 0;
Arrays.fill(values, null);
}
/**
* Compact the {@link Map} backing arrays by rehashing with a capacity just larger than current size
* and giving consideration to the load factor.
*/
public void compact()
{
@DoNotSub final int idealCapacity = (int)Math.round(size() * (1.0d / loadFactor));
rehash(BitUtil.findNextPositivePowerOfTwo(idealCapacity));
}
/**
* {@inheritDoc}
*/
public void putAll(final Map map)
{
for (final Entry entry : map.entrySet())
{
put(entry.getKey(), entry.getValue());
}
}
/**
* {@inheritDoc}
*/
public KeySet keySet()
{
return keySet;
}
/**
* {@inheritDoc}
*/
public Collection values()
{
return valueCollection;
}
/**
* {@inheritDoc}
*/
public Set> entrySet()
{
return entrySet;
}
/**
* {@inheritDoc}
*/
public String toString()
{
final StringBuilder sb = new StringBuilder();
sb.append('{');
for (final Entry entry : entrySet())
{
sb.append(entry.getKey().longValue());
sb.append('=');
sb.append(entry.getValue());
sb.append(", ");
}
if (sb.length() > 1)
{
sb.setLength(sb.length() - 2);
}
sb.append('}');
return sb.toString();
}
/**
* Primitive specialised version of {@link #replace(Object, Object)}
*
* @param key key with which the specified value is associated
* @param value value to be associated with the specified key
* @return the previous value associated with the specified key, or
* {@code null} if there was no mapping for the key.
*/
public V replace(final long key, final V value)
{
V curValue = get(key);
if (curValue != null)
{
curValue = put(key, value);
}
return curValue;
}
/**
* Primitive specialised version of {@link #replace(Object, Object, Object)}
*
* @param key key with which the specified value is associated
* @param oldValue value expected to be associated with the specified key
* @param newValue value to be associated with the specified key
* @return {@code true} if the value was replaced
*/
public boolean replace(final long key, V oldValue, V newValue)
{
final Object curValue = get(key);
if (curValue == null || !Objects.equals(curValue, oldValue))
{
return false;
}
put(key, newValue);
return true;
}
private void increaseCapacity()
{
@DoNotSub final int newCapacity = values.length << 1;
if (newCapacity < 0)
{
throw new IllegalStateException("Max capacity reached at size=" + size);
}
rehash(newCapacity);
}
private void rehash(@DoNotSub final int newCapacity)
{
@DoNotSub final int mask = newCapacity - 1;
/* @DoNotSub */ resizeThreshold = (int)(newCapacity * loadFactor);
final long[] tempKeys = new long[newCapacity];
final Object[] tempValues = new Object[newCapacity];
for (@DoNotSub int i = 0, size = values.length; i < size; i++)
{
final Object value = values[i];
if (null != value)
{
final long key = keys[i];
@DoNotSub int newHash = Hashing.hash(key, mask);
while (null != tempValues[newHash])
{
newHash = ++newHash & mask;
}
tempKeys[newHash] = key;
tempValues[newHash] = value;
}
}
keys = tempKeys;
values = tempValues;
}
private void compactChain(@DoNotSub int deleteIndex)
{
@DoNotSub final int mask = values.length - 1;
@DoNotSub int index = deleteIndex;
while (true)
{
index = ++index & mask;
if (null == values[index])
{
break;
}
@DoNotSub final int hash = Hashing.hash(keys[index], mask);
if ((index < hash && (hash <= deleteIndex || deleteIndex <= index)) ||
(hash <= deleteIndex && deleteIndex <= index))
{
keys[deleteIndex] = keys[index];
values[deleteIndex] = values[index];
values[index] = null;
deleteIndex = index;
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////
// Longernal Sets and Collections
///////////////////////////////////////////////////////////////////////////////////////////////
public class KeySet extends AbstractSet
{
private final KeyIterator iterator = new KeyIterator();
@DoNotSub public int size()
{
return Long2ObjectHashMap.this.size();
}
public boolean contains(final Object o)
{
return Long2ObjectHashMap.this.containsKey(o);
}
public boolean contains(final long key)
{
return Long2ObjectHashMap.this.containsKey(key);
}
public KeyIterator iterator()
{
iterator.reset();
return iterator;
}
public boolean remove(final Object o)
{
return null != Long2ObjectHashMap.this.remove(o);
}
public boolean remove(final long key)
{
return null != Long2ObjectHashMap.this.remove(key);
}
public void clear()
{
Long2ObjectHashMap.this.clear();
}
}
private class ValueCollection extends AbstractCollection
{
private final ValueIterator iterator = new ValueIterator();
@DoNotSub public int size()
{
return Long2ObjectHashMap.this.size();
}
public boolean contains(final Object o)
{
return Long2ObjectHashMap.this.containsValue(o);
}
public ValueIterator iterator()
{
iterator.reset();
return iterator;
}
public void clear()
{
Long2ObjectHashMap.this.clear();
}
}
private class EntrySet extends AbstractSet>
{
private final EntryIterator iterator = new EntryIterator();
@DoNotSub public int size()
{
return Long2ObjectHashMap.this.size();
}
public Iterator> iterator()
{
iterator.reset();
return iterator;
}
public void clear()
{
Long2ObjectHashMap.this.clear();
}
}
///////////////////////////////////////////////////////////////////////////////////////////////
// Iterators
///////////////////////////////////////////////////////////////////////////////////////////////
abstract class AbstractIterator implements Iterator
{
@DoNotSub private int posCounter;
@DoNotSub private int stopCounter;
private boolean isPositionValid = false;
protected long[] keys;
protected Object[] values;
protected AbstractIterator()
{
reset();
}
@DoNotSub protected int position()
{
return posCounter & values.length - 1;
}
public boolean hasNext()
{
@DoNotSub final int mask = values.length - 1;
boolean hasNext = false;
for (@DoNotSub int i = posCounter - 1; i >= stopCounter; i--)
{
@DoNotSub final int index = i & mask;
if (null != values[index])
{
hasNext = true;
break;
}
}
return hasNext;
}
protected void findNext()
{
@DoNotSub final int mask = values.length - 1;
isPositionValid = false;
for (@DoNotSub int i = posCounter - 1; i >= stopCounter; i--)
{
@DoNotSub final int index = i & mask;
if (null != values[index])
{
posCounter = i;
isPositionValid = true;
return;
}
}
throw new NoSuchElementException();
}
public abstract T next();
public void remove()
{
if (isPositionValid)
{
@DoNotSub final int position = position();
values[position] = null;
--size;
compactChain(position);
isPositionValid = false;
}
else
{
throw new IllegalStateException();
}
}
void reset()
{
keys = Long2ObjectHashMap.this.keys;
values = Long2ObjectHashMap.this.values;
@DoNotSub final int capacity = values.length;
@DoNotSub int i = capacity;
if (null != values[capacity - 1])
{
i = 0;
for (@DoNotSub int size = capacity; i < size; i++)
{
if (null == values[i])
{
break;
}
}
}
stopCounter = i;
posCounter = i + capacity;
}
}
public class ValueIterator extends AbstractIterator
{
@SuppressWarnings("unchecked")
public T next()
{
findNext();
return (T)values[position()];
}
}
public class KeyIterator extends AbstractIterator
{
public Long next()
{
return nextLong();
}
public long nextLong()
{
findNext();
return keys[position()];
}
}
@SuppressWarnings("unchecked")
public class EntryIterator
extends AbstractIterator>
implements Entry
{
public Entry next()
{
findNext();
return this;
}
public Long getKey()
{
return keys[position()];
}
public V getValue()
{
return (V)values[position()];
}
public V setValue(final V value)
{
requireNonNull(value);
@DoNotSub final int pos = position();
final Object oldValue = values[pos];
values[pos] = value;
return (V)oldValue;
}
}
}