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

com.hazelcast.util.collection.Long2ObjectHashMap Maven / Gradle / Ivy

/*
 * Original work Copyright 2015 Real Logic Ltd.
 * Modified work Copyright (c) 2015, Hazelcast, Inc. All Rights Reserved.
 *
 * 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 com.hazelcast.util.collection;

import com.hazelcast.util.QuickMath;
import com.hazelcast.util.function.LongFunction;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

import static com.hazelcast.util.Preconditions.checkNotNull;
import static com.hazelcast.util.collection.Hashing.longHash;

/**
 * {@link java.util.Map} implementation specialised for long keys using open addressing and
 * linear probing for cache efficient access.
 *
 * @param  values stored in the {@link java.util.Map}
 */
public class Long2ObjectHashMap implements Map {
    /** The default load factor for constructors not explicitly supplying it */
    public static final double DEFAULT_LOAD_FACTOR = 0.6;
    private final double loadFactor;
    private int resizeThreshold;
    private int capacity;
    private int mask;
    private int size;

    private long[] keys;
    private Object[] values;

    // Cached to avoid allocation.
    private final ValueCollection valueCollection = new ValueCollection();
    private final KeySet keySet = new KeySet();
    private final EntrySet entrySet = new EntrySet();

    public Long2ObjectHashMap() {
        this(8, DEFAULT_LOAD_FACTOR);
    }

    public Long2ObjectHashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * 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(final int initialCapacity, final double loadFactor) {
        this.loadFactor = loadFactor;
        capacity = QuickMath.nextPowerOfTwo(initialCapacity);
        mask = capacity - 1;
        resizeThreshold = (int) (capacity * loadFactor);

        keys = new long[capacity];
        values = new Object[capacity];
    }

    /**
     * Get the load factor beyond which the map will increase size.
     *
     * @return load factor for when the map should increase size.
     */
    public double 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.
     */
    public int capacity() {
        return capacity;
    }

    /**
     * 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.
     */
    public int resizeThreshold() {
        return resizeThreshold;
    }

    /**
     * {@inheritDoc}
     */
    public int size() {
        return size;
    }

    /**
     * {@inheritDoc}
     */
    public boolean isEmpty() {
        return 0 == size;
    }

    /**
     * {@inheritDoc}
     */
    public boolean containsKey(final Object key) {
        checkNotNull(key, "Null keys are not permitted");
        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) {
        int index = longHash(key, mask);
        while (null != values[index]) {
            if (key == keys[index]) {
                return true;
            }
            index = ++index & mask;
        }
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public boolean containsValue(final Object value) {
        checkNotNull(value, "Null values are not permitted");
        for (final Object v : values) {
            if (null != v && value.equals(v)) {
                return true;
            }
        }
        return false;
    }

    /**
     * {@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) {
        int index = longHash(key, mask);
        Object value;
        while (null != (value = values[index])) {
            if (key == keys[index]) {
                return (V) value;
            }
            index = ++index & mask;
        }
        return null;
    }

    /**
     * Get a value for a given key, or if it does ot exist then default the value via a {@link LongFunction}
     * and put it in the map.
     * 

* * @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) { checkNotNull(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 int 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) { checkNotNull(value, "Value cannot be null"); V oldValue = null; int index = longHash(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 int key. * * @param key for indexing the {@link Map} * @return the value if found otherwise null */ @SuppressWarnings("unchecked") public V remove(final long key) { int index = longHash(key, mask); Object value; while (null != (value = values[index])) { if (key == keys[index]) { values[index] = null; --size; compactChain(index); return (V) value; } index = ++index & mask; } return null; } /** * {@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() { final int idealCapacity = (int) Math.round(size() * (1.0d / loadFactor)); rehash(QuickMath.nextPowerOfTwo(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} * This set's iterator also implements Map.Entry * so the next() method can just return the iterator * instance itself with no heap allocation. This characteristic * makes the set unusable wherever the returned entries are * retained (such as coll.addAll(entrySet). */ public Set> entrySet() { return entrySet; } /** * {@inheritDoc} */ public String toString() { final StringBuilder sb = new StringBuilder(); sb.append('{'); for (final Map.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(); } private void increaseCapacity() { final int newCapacity = capacity << 1; if (newCapacity < 0) { throw new IllegalStateException("Max capacity reached at size=" + size); } rehash(newCapacity); } private void rehash(final int newCapacity) { if (1 != Integer.bitCount(newCapacity)) { throw new IllegalStateException("New capacity must be a power of two"); } capacity = newCapacity; mask = newCapacity - 1; resizeThreshold = (int) (newCapacity * loadFactor); final long[] tempKeys = new long[capacity]; final Object[] tempValues = new Object[capacity]; for (int i = 0, size = values.length; i < size; i++) { final Object value = values[i]; if (null != value) { final long key = keys[i]; int newHash = longHash(key, mask); while (null != tempValues[newHash]) { newHash = ++newHash & mask; } tempKeys[newHash] = key; tempValues[newHash] = value; } } keys = tempKeys; values = tempValues; } private void compactChain(int deleteIndex) { int index = deleteIndex; while (true) { index = ++index & mask; if (null == values[index]) { return; } final int hash = longHash(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; } } } /////////////////////////////////////////////////////////////////////////////////////////////// // Internal Sets and Collections /////////////////////////////////////////////////////////////////////////////////////////////// public class KeySet extends AbstractSet { public int size() { return Long2ObjectHashMap.this.size(); } public boolean isEmpty() { return Long2ObjectHashMap.this.isEmpty(); } 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() { return new KeyIterator(); } 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 { public int size() { return Long2ObjectHashMap.this.size(); } public boolean isEmpty() { return Long2ObjectHashMap.this.isEmpty(); } public boolean contains(final Object o) { return Long2ObjectHashMap.this.containsValue(o); } public ValueIterator iterator() { return new ValueIterator(); } public void clear() { Long2ObjectHashMap.this.clear(); } } private class EntrySet extends AbstractSet> { public int size() { return Long2ObjectHashMap.this.size(); } public boolean isEmpty() { return Long2ObjectHashMap.this.isEmpty(); } public Iterator> iterator() { return new EntryIterator(); } public void clear() { Long2ObjectHashMap.this.clear(); } } /////////////////////////////////////////////////////////////////////////////////////////////// // Iterators /////////////////////////////////////////////////////////////////////////////////////////////// private abstract class AbstractIterator implements Iterator { protected final long[] keys = Long2ObjectHashMap.this.keys; protected final Object[] values = Long2ObjectHashMap.this.values; private int posCounter; private int stopCounter; private boolean isPositionValid; protected AbstractIterator() { int i = capacity; if (null != values[capacity - 1]) { i = 0; for (int size = capacity; i < size; i++) { if (null == values[i]) { break; } } } stopCounter = i; posCounter = i + capacity; } protected int getPosition() { return posCounter & mask; } public boolean hasNext() { for (int i = posCounter - 1; i >= stopCounter; i--) { final int index = i & mask; if (null != values[index]) { return true; } } return false; } protected void findNext() { isPositionValid = false; for (int i = posCounter - 1; i >= stopCounter; i--) { 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) { final int position = getPosition(); values[position] = null; --size; compactChain(position); isPositionValid = false; } else { throw new IllegalStateException(); } } } public class ValueIterator extends AbstractIterator { @SuppressWarnings("unchecked") public T next() { findNext(); return (T) values[getPosition()]; } } public class KeyIterator extends AbstractIterator { public Long next() { return nextLong(); } public long nextLong() { findNext(); return keys[getPosition()]; } } @SuppressWarnings("unchecked") @SuppressFBWarnings(value = "PZ_DONT_REUSE_ENTRY_OBJECTS_IN_ITERATORS", justification = "deliberate, documented choice") public class EntryIterator extends AbstractIterator> implements Entry { public Entry next() { findNext(); return this; } public Long getKey() { return keys[getPosition()]; } public V getValue() { return (V) values[getPosition()]; } public V setValue(final V value) { checkNotNull(value); final int pos = getPosition(); final Object oldValue = values[pos]; values[pos] = value; return (V) oldValue; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy