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

alluxio.collections.ConcurrentIdentityHashMap Maven / Gradle / Ivy

There is a newer version: 313
Show newest version
/*
 * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
 * (the "License"). You may not use this work except in compliance with the License, which is
 * available at www.apache.org/licenses/LICENSE-2.0
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied, as more fully set forth in the License.
 *
 * See the NOTICE file distributed with this work for information regarding copyright ownership.
 */

package alluxio.collections;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import javax.annotation.concurrent.ThreadSafe;

/**
 * An implementation {@link ConcurrentHashMap} that utilizes identity semantics
 * with its keys
 *
 * Having identity semantics means that it is possible to have two objects with which are
 * equal based on the object's {@code equals()} implementation, but not be overridden in the hash
 * map if you try to put both. For example:
 *
 * 
 *   ConcurrentIdentityHashMap<String, Boolean> map = new ConcurrentIdentityHashMap<>();
 *   String s1 = new String("test");
 *   String s2 = new String("test");
 *   map.put(s1, true);
 *   map.put(s2, false);
 *   map.size(); // Prints "2".
 *   map.get(s1); // true
 *   map.get(s2); // false
 * 
* * Keys in this mapped are hashed based on the identity, that is, the location in memory of the * objects rather than the equality which java typically uses for comparison. * * This implementation of {@link ConcurrentMap} has a few limitations which the corresponding * {@link ConcurrentHashMap} does not have. * * - This map's {@link #entrySet()} returns a copy of the entries at the time of calling the method. * The returned set does will not receive updates from the underlying map. This is a departure * from the behavior of {@link ConcurrentHashMap} * * - This map's {@link #keySet()} does return the map-backed keySet and provides the same * behavior as in {@link ConcurrentHashMap}, however the difference is that {@link Set#toArray()} * and {@link Set#toArray(Object[])} methods are left unimplemented and will throw an error if * the user attempts to convert the set into an array. * * @param the type of keys maintained by this map * @param the type of mapped values */ @ThreadSafe public class ConcurrentIdentityHashMap implements ConcurrentMap { private static final String UNSUPPORTED_OP_FMT = "%s is not supported in this set returned from " + ConcurrentIdentityHashMap.class.getCanonicalName(); private final ConcurrentHashMap, V> mInternalMap; /** * Creates a new, empty map with the default initial table size (16). */ public ConcurrentIdentityHashMap() { mInternalMap = new ConcurrentHashMap<>(); } /** * Creates a new, empty map with an initial table size accommodating the specified number of * elements without having to dynamically resize. * * @param initialCapacity The implementation performs internal sizing to accommodate this many * elements. * @throws IllegalArgumentException if the initial capacity of * elements is negative */ public ConcurrentIdentityHashMap(int initialCapacity) { mInternalMap = new ConcurrentHashMap<>(initialCapacity); } /** * Creates a new, empty map with an initial table size based on the given number of elements * ({@code initialCapacity}) and initial load factor ({@code loadFactor}) with a * {@code concurrencyLevel} of 1. * * @param initialCapacity the initial capacity. The implementation performs internal sizing to * accommodate this many elements, given the specified load factor. * @param loadFactor the load factor (table density) for * establishing the initial table size * @throws IllegalArgumentException if the initial capacity of * elements is negative or the load factor is nonpositive */ public ConcurrentIdentityHashMap(int initialCapacity, float loadFactor) { this(initialCapacity, loadFactor, 1); } /** * Creates a new, empty map with an initial table size based on the given number of elements * ({@code initialCapacity}), load factor ({@code loadFactor}), and number of * expected concurrently updating threads ({@code concurrencyLevel}). * * @param initialCapacity the initial capacity. The implementation performs internal sizing to * accommodate this many elements, given the specified load factor. * @param loadFactor the load factor (table density) for * establishing the initial table size * @param concurrencyLevel the estimated number of concurrently updating threads. The * implementation may use this value as a sizing hint. * @throws IllegalArgumentException if the initial capacity is negative or the load factor or * concurrencyLevel are nonpositive */ public ConcurrentIdentityHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { mInternalMap = new ConcurrentHashMap<>(initialCapacity, loadFactor, concurrencyLevel); } @Override public boolean containsKey(Object k) { return mInternalMap.containsKey(new IdentityObject<>(k)); } @Override public V put(K k, V v) { return mInternalMap.put(new IdentityObject<>(k), v); } @Override public V putIfAbsent(K k, V v) { return mInternalMap.putIfAbsent(new IdentityObject<>(k), v); } @Override public boolean remove(Object k, Object v) { return mInternalMap.remove(new IdentityObject<>(k), v); } @Override public V remove(Object k) { return mInternalMap.remove(new IdentityObject<>(k)); } @Override public boolean replace(K k, V v1, V v2) { return mInternalMap.replace(new IdentityObject<>(k), v1, v2); } @Override public V replace(K k, V v) { return mInternalMap.replace(new IdentityObject<>(k), v); } @Override public V get(Object k) { return mInternalMap.get(new IdentityObject<>(k)); } @Override public Collection values() { return mInternalMap.values(); } @Override public void clear() { mInternalMap.clear(); } /** * Returns a set representing the entries of this map at the time of calling. * * Note, this method will create a copy of the map's entry set at creation time. Standard * behavior in the {@link ConcurrentHashMap} says that the entry set backed by the map is * updated when the underlying map is updated. That is not the case for this class. * * @return The set of entries in the map at time of calling */ @Override public Set> entrySet() { return mInternalMap.entrySet().stream().map(IdentityEntry::new) .collect(Collectors.toSet()); } @Override public void putAll(Map map) { map.forEach((k, v) -> mInternalMap.put(new IdentityObject<>(k), v)); } @Override public boolean isEmpty() { return mInternalMap.isEmpty(); } @Override public int size() { return mInternalMap.size(); } @Override public boolean containsValue(Object value) { return mInternalMap.containsValue(value); } /** * Returns a set representing all keys contained within the map. * * Note this method departs slightly from the standard {@link ConcurrentHashMap} implementation * by providing its own implementation of a Set for {@link IdentityObject}. Not all methods * have been implemented. Namely the {@code toArray}, {@code add*}, and * {@code *all(Collection c)} methods. * * @return The set of keys in the map */ @Override public Set keySet() { Set> set = mInternalMap.keySet(); return new Set() { @Override public int size() { return set.size(); } @Override public boolean isEmpty() { return set.isEmpty(); } @Override public boolean contains(Object o) { return set.contains(new IdentityObject<>(o)); } @Override public Iterator iterator() { return set.stream().map(IdentityObject::get).iterator(); } @Override public Object[] toArray() { throw new UnsupportedOperationException(String.format(UNSUPPORTED_OP_FMT, "toArray")); } @Override public T[] toArray(T[] a) { throw new UnsupportedOperationException(String.format(UNSUPPORTED_OP_FMT, "toArray")); } @Override public boolean add(K k) { throw new UnsupportedOperationException(String.format(UNSUPPORTED_OP_FMT, "add")); } @Override public boolean remove(Object o) { return set.remove(new IdentityObject<>(o)); } /* * This method is possible to implement if inputs from the input collection are auto-boxed * into an IdentityObject but the implementation has been left absent for now. */ @Override public boolean containsAll(Collection c) { throw new UnsupportedOperationException(String.format(UNSUPPORTED_OP_FMT, "containsAll")); } @Override public boolean addAll(Collection c) { throw new UnsupportedOperationException(String.format(UNSUPPORTED_OP_FMT, "addAll")); } /* * This method is possible to implement if inputs from the input collection are auto-boxed * into an IdentityObject but the implementation has been left absent for now. */ @Override public boolean retainAll(Collection c) { throw new UnsupportedOperationException(String.format(UNSUPPORTED_OP_FMT, "retainAll")); } /* * This method is possible to implement if inputs from the input collection are auto-boxed * into an IdentityObject but the implementation has been left absent for now. */ @Override public boolean removeAll(Collection c) { throw new UnsupportedOperationException(String.format(UNSUPPORTED_OP_FMT, "removeAll")); } @Override public void clear() { set.clear(); } }; } private class IdentityObject { private T mObj; public IdentityObject(T o) { mObj = o; } public T get() { return mObj; } /* * This method hashes on top of the {@link System#identityHashCode(Object)} because the * {@link ConcurrentHashMap} implementation uses a bucketing strategy where hash collisions * occur for when values do not differ in upper or lower bits. Using the extra hash here * helps ensure more even spread among the buckets of the internal map */ @Override public int hashCode() { // Robert Jenkins 32-bit integer hash int a = System.identityHashCode(mObj); a = (a + 0x7ed55d16) + (a << 12); a = (a ^ 0xc761c23c) ^ (a >> 19); a = (a + 0x165667b1) + (a << 5); a = (a + 0xd3a2646c) ^ (a << 9); a = (a + 0xfd7046c5) + (a << 3); a = (a ^ 0xb55a4f09) ^ (a >> 16); return a; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof IdentityObject)) { return false; } try { IdentityObject other = (IdentityObject) o; return mObj == other.mObj; } catch (ClassCastException e) { return false; } } } private class IdentityEntry implements Map.Entry { private final IdentityObject mKey; private P mValue; IdentityEntry(Map.Entry, P> e) { mKey = e.getKey(); mValue = e.getValue(); } @Override public T getKey() { return mKey.get(); } @Override public P getValue() { return mValue; } @Override public P setValue(P v) { P old = mValue; mValue = v; return old; } @Override public int hashCode() { return Objects.hash(mKey, mValue); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof IdentityEntry)) { return false; } try { IdentityEntry other = (IdentityEntry) o; return mKey.equals(other.mKey) && mValue.equals(other.mValue); } catch (ClassCastException e) { return false; } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy