| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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.apache.logging.log4j.perf.nogc; |
| |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.ConcurrentModificationException; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Objects; |
| |
| import org.apache.logging.log4j.util.ReadOnlyStringMap; |
| import org.apache.logging.log4j.util.StringMap; |
| import org.apache.logging.log4j.spi.ThreadContextMap; |
| import org.apache.logging.log4j.util.BiConsumer; |
| import org.apache.logging.log4j.util.TriConsumer; |
| |
| /** |
| * Open hash map-based implementation of the {@code ReadOnlyStringMap} interface. |
| * Implementation based on <a href="http://fastutil.di.unimi.it/">fastutil</a>'s |
| * <a href="http://fastutil.di.unimi.it/docs/it/unimi/dsi/fastutil/objects/Object2ObjectOpenHashMap.html">Object2ObjectOpenHashMap</a>. |
| * <p> |
| * A type-specific hash map with a fast, small-footprint implementation. |
| * |
| * <P> |
| * Instances of this class use a hash table to represent a map. The table is |
| * filled up to a specified <em>load factor</em>, and then doubled in size to |
| * accommodate new entries. If the table is emptied below <em>one fourth</em> of |
| * the load factor, it is halved in size. However, halving is not performed when |
| * deleting entries from an iterator, as it would interfere with the iteration |
| * process. |
| * |
| * <p> |
| * Note that {@link #clear()} does not modify the hash table size. Rather, the |
| * {@link #trim(int)} method lets you control the size of |
| * the table; this is particularly useful if you reuse instances of this class. |
| * <p> |
| * <ul> |
| * <li>Garbage-free iteration over key-value pairs with {@code BiConsumer} and {@code TriConsumer}.</li> |
| * <li>Fast copy. If the ThreadContextMap is also an instance of {@code OpenHashStringMap}, |
| * the full thread context data can be transferred with two array copies and five field updates.</li> |
| * </ul> |
| * |
| * @since 2.7 |
| */ |
| public class OpenHashStringMap<K, V> implements StringMap, ThreadContextMap { |
| /** The initial default size of a hash table. */ |
| public static final int DEFAULT_INITIAL_SIZE = 16; |
| |
| /** The default load factor of a hash table. */ |
| public static final float DEFAULT_LOAD_FACTOR = .75f; |
| |
| private static final String FROZEN = "Frozen collection cannot be modified"; |
| private static final long serialVersionUID = -1486744623338827187L; |
| |
| /** The array of keys. */ |
| protected transient K[] keys; |
| /** The array of values. */ |
| protected transient V[] values; |
| /** The mask for wrapping a position counter. */ |
| protected transient int mask; |
| /** Whether this set contains the key zero. */ |
| protected transient boolean containsNullKey; |
| /** The current table size. */ |
| protected transient int arraySize; |
| /** |
| * Threshold after which we rehash. It must be the table size times {@link #loadFactor}. |
| */ |
| protected transient int maxFill; |
| /** Number of entries in the set (including the key zero, if present). */ |
| protected int size; |
| /** The acceptable load factor. */ |
| protected final float loadFactor; |
| |
| private final V defRetValue = null; |
| private boolean immutable; |
| private transient boolean iterating; |
| |
| /** |
| * Creates a new hash map with initial expected |
| * {@link #DEFAULT_INITIAL_SIZE} entries and |
| * {@link #DEFAULT_LOAD_FACTOR} as load factor. |
| */ |
| public OpenHashStringMap() { |
| this(DEFAULT_INITIAL_SIZE, DEFAULT_LOAD_FACTOR); |
| } |
| /** |
| * Creates a new hash map with {@link #DEFAULT_LOAD_FACTOR} as load factor. |
| * |
| * @param expected |
| * the expected number of elements in the hash map. |
| */ |
| public OpenHashStringMap(final int expected) { |
| this(expected, DEFAULT_LOAD_FACTOR); |
| } |
| /** |
| * Creates a new hash map. |
| * |
| * <p> |
| * The actual table size will be the least power of two greater than |
| * <code>expected</code>/<code>f</code>. |
| * |
| * @param expected |
| * the expected number of elements in the hash set. |
| * @param f |
| * the load factor. |
| */ |
| @SuppressWarnings("unchecked") |
| public OpenHashStringMap(final int expected, final float f) { |
| if (f <= 0 || f > 1) { |
| throw new IllegalArgumentException( |
| "Load factor must be greater than 0 and smaller than or equal to 1"); |
| } |
| if (expected < 0){ |
| throw new IllegalArgumentException( |
| "The expected number of elements must be nonnegative"); |
| } |
| this.loadFactor = f; |
| arraySize = HashCommon.arraySize(expected, f); |
| mask = arraySize - 1; |
| maxFill = HashCommon.maxFill(arraySize, f); |
| keys = (K[]) new Object[arraySize + 1]; |
| values = (V[]) new Object[arraySize + 1]; |
| } |
| /** |
| * Creates a new hash map with {@link #DEFAULT_LOAD_FACTOR} as load |
| * factor copying a given one. |
| * |
| * @param map |
| * a {@link Map} to be copied into the new hash map. |
| */ |
| public OpenHashStringMap(final Map<? extends K, ? extends V> map) { |
| this(map, DEFAULT_LOAD_FACTOR); |
| } |
| /** |
| * Creates a new hash map copying a given one. |
| * |
| * @param map |
| * a {@link Map} to be copied into the new hash map. |
| * @param f |
| * the load factor. |
| */ |
| public OpenHashStringMap(final Map<? extends K, ? extends V> map, final float f) { |
| this(map.size(), f); |
| putAll(map); |
| } |
| |
| /** |
| * Creates a new hash map with {@link #DEFAULT_LOAD_FACTOR} as load |
| * factor copying a given type-specific one. |
| * |
| * @param contextData |
| * a type-specific map to be copied into the new hash map. |
| */ |
| public OpenHashStringMap(final ReadOnlyStringMap contextData) { |
| this(contextData, DEFAULT_LOAD_FACTOR); |
| } |
| /** |
| * Creates a new hash map copying a given type-specific one. |
| * |
| * @param contextData |
| * a type-specific map to be copied into the new hash map. |
| * @param f |
| * the load factor. |
| */ |
| public OpenHashStringMap(final ReadOnlyStringMap contextData, final float f) { |
| this(contextData.size(), f); |
| if (contextData instanceof OpenHashStringMap) { |
| initFrom0((OpenHashStringMap) contextData); |
| } else { |
| contextData.forEach(PUT_ALL, this); |
| } |
| } |
| private static final TriConsumer<String, Object, StringMap> PUT_ALL = |
| (key, value, contextData) -> contextData.putValue(key, value); |
| |
| private void assertNotFrozen() { |
| if (immutable) { |
| throw new UnsupportedOperationException(FROZEN); |
| } |
| } |
| |
| private void assertNoConcurrentModification() { |
| if (iterating) { |
| throw new ConcurrentModificationException(); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| private void initFrom0(final OpenHashStringMap other) { |
| // this.loadFactor = other.loadFactor; // final field |
| this.arraySize = other.arraySize; |
| this.size = other.size; |
| this.containsNullKey = other.containsNullKey; |
| this.mask = other.mask; |
| this.maxFill = other.maxFill; |
| keys = (K[]) Arrays.copyOf(other.keys, arraySize + 1); |
| values = (V[]) Arrays.copyOf(other.values, arraySize + 1); |
| } |
| |
| private int realSize() { |
| return containsNullKey ? size - 1 : size; |
| } |
| |
| private void ensureCapacity(final int capacity) { |
| final int needed = HashCommon.arraySize(capacity, loadFactor); |
| if (needed > arraySize) { |
| rehash(needed); |
| } |
| } |
| |
| private void tryCapacity(final long capacity) { |
| final int needed = Math.min( |
| 1 << 30, Math.max(2, HashCommon.nextPowerOfTwo((int) Math.ceil(capacity / loadFactor)))); |
| if (needed > arraySize) { |
| rehash(needed); |
| } |
| } |
| |
| @Override |
| public Map<String, String> toMap() { |
| final Map<String, String> result = new HashMap<>(size); |
| forEach(COPY_INTO_MAP, result); |
| return result; |
| } |
| |
| private static final TriConsumer<String, Object, Map<String, String>> COPY_INTO_MAP = |
| (k, v, map) -> map.put(k, v == null ? null : v.toString()); |
| |
| /* |
| * Removes all elements from this map. |
| * |
| * <P>To increase object reuse, this method does not change the table size. |
| * If you want to reduce the table size, you must use {@link #trim()}. |
| */ |
| @Override |
| public void clear() { |
| if (size == 0) { |
| return; |
| } |
| assertNotFrozen(); |
| assertNoConcurrentModification(); |
| |
| size = 0; |
| containsNullKey = false; |
| Arrays.fill(keys, (null)); |
| Arrays.fill(values, null); |
| } |
| |
| @Override |
| public boolean containsKey(final String key) { |
| return containsObjectKey(key); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private boolean containsObjectKey(final Object k) { |
| if (k == null) { |
| return containsNullKey; |
| } |
| K curr; |
| final K[] key = this.keys; |
| int pos; |
| // The starting point. |
| if ((curr = key[pos = HashCommon.mix(k.hashCode()) & mask]) == null) { |
| return false; |
| } |
| if (k.equals(curr)) { |
| return true; |
| } |
| // There's always an unused entry. |
| while (true) { |
| if ((curr = key[pos = (pos + 1) & mask]) == null) { |
| return false; |
| } |
| if (k.equals(curr)) { |
| return true; |
| } |
| } |
| } |
| |
| @Override |
| public boolean equals(final Object obj) { |
| if (obj == this) { |
| return true; |
| } |
| if (!(obj instanceof ReadOnlyStringMap)) { |
| return false; |
| } |
| final ReadOnlyStringMap other = (ReadOnlyStringMap) obj; |
| if (other.size() != size()) { |
| return false; |
| } |
| int pos = arraySize; |
| if (containsNullKey) { |
| if (!Objects.equals(getObjectValue(null), other.getValue(null))) { |
| return false; |
| } |
| } |
| --pos; |
| final K myKeys[] = this.keys; |
| for (; pos >= 0; pos--) { |
| K k; |
| if ((k = myKeys[pos]) != null) { |
| if (!Objects.equals(values[pos], other.getValue((String) k))) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public <VAL> void forEach(final BiConsumer<String, ? super VAL> action) { |
| final int startSize = size; |
| final K myKeys[] = this.keys; |
| int pos = arraySize; |
| |
| iterating = true; |
| try { |
| if (containsNullKey) { |
| action.accept((String) myKeys[pos], (VAL) values[pos]); |
| if (size != startSize) { |
| throw new ConcurrentModificationException(); |
| } |
| } |
| --pos; |
| for (; pos >= 0; pos--) { |
| if (myKeys[pos] != null) { |
| action.accept((String) myKeys[pos], (VAL) values[pos]); |
| if (size != startSize) { |
| throw new ConcurrentModificationException(); |
| } |
| } |
| } |
| } finally { |
| iterating = false; |
| } |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public <VAL, STATE> void forEach(final TriConsumer<String, ? super VAL, STATE> action, final STATE state) { |
| final int startSize = size; |
| final K myKeys[] = this.keys; |
| int pos = arraySize; |
| |
| iterating = true; |
| try { |
| if (containsNullKey) { |
| action.accept((String) myKeys[pos], (VAL) values[pos], state); |
| if (size != startSize) { |
| throw new ConcurrentModificationException(); |
| } |
| } |
| --pos; |
| for (; pos >= 0; pos--) { |
| if (myKeys[pos] != null) { |
| action.accept((String) myKeys[pos], (VAL) values[pos], state); |
| if (size != startSize) { |
| throw new ConcurrentModificationException(); |
| } |
| } |
| } |
| } finally { |
| iterating = false; |
| } |
| } |
| |
| @Override |
| public String get(final String key) { |
| return (String) getObjectValue(key); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private V getObjectValue(final Object k) { |
| if (k == null) { |
| return containsNullKey ? values[arraySize] : defRetValue; |
| } |
| K curr; |
| final K[] key = this.keys; |
| int pos; |
| // The starting point. |
| if ((curr = key[pos = HashCommon.mix(k.hashCode()) & mask]) == null) { |
| return defRetValue; |
| } |
| if (k.equals(curr)) { |
| return values[pos]; |
| } |
| // There's always an unused entry. |
| while (true) { |
| if (((curr = key[pos = (pos + 1) & mask]) == null)) { |
| return defRetValue; |
| } |
| if (k.equals(curr)) { |
| return values[pos]; |
| } |
| } |
| } |
| |
| @Override |
| public Map<String, String> getCopy() { |
| return toMap(); |
| } |
| |
| @Override |
| public Map<String, String> getImmutableMapOrNull() { |
| return isEmpty() ? null : Collections.unmodifiableMap(toMap()); |
| } |
| |
| @Override |
| public <VAL> VAL getValue(final String key) { |
| return (VAL) getObjectValue(key); |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| return size == 0; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public void put(final String key, final String value) { |
| putObjectValue((K) key, (V) value); |
| } |
| |
| private int insert(final K k, final V v) { |
| int pos; |
| if (k == null) { |
| if (containsNullKey) { |
| return arraySize; |
| } |
| containsNullKey = true; |
| pos = arraySize; |
| } else { |
| K curr; |
| final K[] key = this.keys; |
| // The starting point. |
| if (!((curr = key[pos = HashCommon.mix(k.hashCode()) & mask]) == null)) { |
| if (curr.equals(k)) { |
| return pos; |
| } |
| while (!((curr = key[pos = (pos + 1) & mask]) == null)) { |
| if (curr.equals(k)) { |
| return pos; |
| } |
| } |
| } |
| } |
| keys[pos] = k; |
| values[pos] = v; |
| if (size++ >= maxFill) { |
| rehash(HashCommon.arraySize(size + 1, loadFactor)); |
| } |
| return -1; |
| } |
| |
| @Override |
| public void putAll(final ReadOnlyStringMap source) { |
| assertNotFrozen(); |
| assertNoConcurrentModification(); |
| |
| if (size() == 0 && source instanceof OpenHashStringMap) { |
| initFrom0((OpenHashStringMap) source); |
| } else if (source != null) { |
| source.forEach(PUT_ALL, this); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public void putAll(final Map<? extends K, ? extends V> map) { |
| if (loadFactor <= .5) { |
| // The resulting map will be sized for m.size() elements |
| ensureCapacity(map.size()); |
| } else { |
| // The resulting map will be tentatively sized for size() + m.size() elements |
| tryCapacity(size() + map.size()); |
| } |
| for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) { |
| putObjectValue(entry.getKey(), entry.getValue()); |
| } |
| } |
| |
| private V putObjectValue(final K k, final V v) { |
| assertNotFrozen(); |
| assertNoConcurrentModification(); |
| |
| final int pos = insert(k, v); |
| if (pos < 0) { |
| return defRetValue; |
| } |
| final V oldValue = values[pos]; |
| values[pos] = v; |
| return oldValue; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public void putValue(final String key, final Object value) { |
| putObjectValue((K) key, (V) value); |
| } |
| |
| @Override |
| public void remove(final String key) { |
| removeObjectKey(key); |
| } |
| |
| @Override |
| public void freeze() { |
| immutable = true; |
| } |
| |
| @Override |
| public boolean isFrozen() { |
| return immutable; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private V removeObjectKey(final Object k) { |
| assertNotFrozen(); |
| assertNoConcurrentModification(); |
| |
| if (k == null) { |
| if (containsNullKey) { |
| return removeNullEntry(); |
| } |
| return defRetValue; |
| } |
| final K[] key = this.keys; |
| int pos = HashCommon.mix(k.hashCode()) & mask; |
| K curr = key[pos & mask]; |
| // The starting point. |
| if (curr == null) { |
| return defRetValue; |
| } |
| if (k.equals(curr)) { |
| return removeEntry(pos); |
| } |
| while (true) { |
| if ((curr = key[pos = (pos + 1) & mask]) == null) { |
| return defRetValue; |
| } |
| if (k.equals(curr)) { |
| return removeEntry(pos); |
| } |
| } |
| } |
| private V removeEntry(final int pos) { |
| final V oldValue = values[pos]; |
| values[pos] = null; |
| size--; |
| shiftKeys(pos); |
| if (size < maxFill / 4 && arraySize > DEFAULT_INITIAL_SIZE) { |
| rehash(arraySize / 2); |
| } |
| return oldValue; |
| } |
| private V removeNullEntry() { |
| containsNullKey = false; |
| keys[arraySize] = null; |
| final V oldValue = values[arraySize]; |
| values[arraySize] = null; |
| size--; |
| if (size < maxFill / 4 && arraySize > DEFAULT_INITIAL_SIZE) { |
| rehash(arraySize / 2); |
| } |
| return oldValue; |
| } |
| /** |
| * Shifts left entries with the specified hash code, starting at the |
| * specified position, and empties the resulting free entry. |
| * |
| * @param pos |
| * a starting position. |
| */ |
| private void shiftKeys(int pos) { |
| // Shift entries with the same hash. |
| int last, slot; |
| K curr; |
| final K[] myKeys = this.keys; |
| for (;;) { |
| pos = ((last = pos) + 1) & mask; |
| for (;;) { |
| if (((curr = myKeys[pos]) == null)) { |
| myKeys[last] = (null); |
| values[last] = null; |
| return; |
| } |
| slot = HashCommon.mix(curr.hashCode()) & mask; |
| if (last <= pos ? (last >= slot || slot > pos) : (last >= slot && slot > pos)) { |
| break; |
| } |
| pos = (pos + 1) & mask; |
| } |
| myKeys[last] = curr; |
| values[last] = values[pos]; |
| } |
| } |
| |
| @Override |
| public int size() { |
| return size; |
| } |
| |
| /** |
| * Rehashes this map if the table is too large. |
| * |
| * <P> |
| * Let <var>N</var> be the smallest table size that can hold |
| * <code>max(n,{@link #size()})</code> entries, still satisfying the load |
| * factor. If the current table size is smaller than or equal to |
| * <var>N</var>, this method does nothing. Otherwise, it rehashes this map |
| * in a table of size <var>N</var>. |
| * |
| * <P> |
| * This method is useful when reusing maps. {@linkplain #clear() Clearing a |
| * map} leaves the table size untouched. If you are reusing a map many times, |
| * you can call this method with a typical size to avoid keeping around a |
| * very large table just because of a few large transient maps. |
| * |
| * @param n |
| * the threshold for the trimming. |
| * @return true if there was enough memory to trim the map. |
| */ |
| public boolean trim(final int n) { |
| final int l = HashCommon.nextPowerOfTwo((int) Math.ceil(n / loadFactor)); |
| if (l >= n || size > HashCommon.maxFill(l, loadFactor)) { |
| return true; |
| } |
| try { |
| rehash(l); |
| } catch (final OutOfMemoryError cantDoIt) { // unusual to catch OOME but in this case appropriate |
| return false; |
| } |
| return true; |
| } |
| /** |
| * Rehashes the map. |
| * |
| * <P> |
| * This method implements the basic rehashing strategy, and may be overriden |
| * by subclasses implementing different rehashing strategies (e.g., |
| * disk-based rehashing). However, you should not override this method |
| * unless you understand the internal workings of this class. |
| * |
| * @param newN |
| * the new size |
| */ |
| @SuppressWarnings("unchecked") |
| protected void rehash(final int newN) { |
| final K myKeys[] = this.keys; |
| final V myValues[] = this.values; |
| final int mask = newN - 1; // Note that this is used by the hashing |
| // macro |
| final K newKey[] = (K[]) new Object[newN + 1]; |
| final V newValue[] = (V[]) new Object[newN + 1]; |
| int i = arraySize, pos; |
| for (int j = realSize(); j-- != 0;) { |
| while (myKeys[--i] == null) { |
| // advance i until we find an existing key |
| } |
| if (newKey[pos = HashCommon.mix(myKeys[i].hashCode()) & mask] != null) { // rehash & check slot availability |
| while (newKey[pos = (pos + 1) & mask] != null) { |
| // find available slot at (or immediately following) pos |
| } |
| } |
| newKey[pos] = myKeys[i]; |
| newValue[pos] = myValues[i]; |
| } |
| newValue[newN] = myValues[arraySize]; |
| arraySize = newN; |
| this.mask = mask; |
| maxFill = HashCommon.maxFill(arraySize, loadFactor); |
| this.keys = newKey; |
| this.values = newValue; |
| } |
| |
| /** |
| * Returns a hash code for this map. |
| * |
| * @return a hash code for this map. |
| */ |
| @Override |
| public int hashCode() { |
| int result = 0; |
| for (int j = realSize(), i = 0, t = 0; j-- != 0;) { |
| while (keys[i] == null) { |
| i++; |
| } |
| if (this != keys[i]) { |
| t = keys[i].hashCode(); |
| } |
| if (this != values[i]) { |
| t ^= (values[i] == null ? 0 : values[i].hashCode()); |
| } |
| result += t; |
| i++; |
| } |
| // Zero / null keys have hash zero. |
| if (containsNullKey) { |
| result += (values[arraySize] == null ? 0 : values[arraySize].hashCode()); |
| } |
| return result; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private void readObject(final ObjectInputStream s) throws IOException, ClassNotFoundException { |
| s.defaultReadObject(); |
| arraySize = HashCommon.arraySize(size, loadFactor); |
| maxFill = HashCommon.maxFill(arraySize, loadFactor); |
| mask = arraySize - 1; |
| final K key[] = this.keys = (K[]) new Object[arraySize + 1]; |
| final V value[] = this.values = (V[]) new Object[arraySize + 1]; |
| K k; |
| V v; |
| for (int i = size, pos; i-- != 0;) { |
| k = (K) s.readObject(); |
| v = (V) s.readObject(); |
| if (k == null) { |
| pos = arraySize; |
| containsNullKey = true; |
| } else { |
| pos = HashCommon.mix(k.hashCode()) & mask; |
| while (key[pos] != null) { |
| pos = (pos + 1) & mask; |
| } |
| } |
| key[pos] = k; |
| value[pos] = v; |
| } |
| } |
| |
| private void writeObject(final ObjectOutputStream s) throws IOException { |
| s.defaultWriteObject(); |
| try { |
| forEach(SERIALIZER, s); |
| } catch (final RuntimeException runex) { |
| if (runex.getCause() instanceof IOException) { |
| throw (IOException) runex.getCause(); |
| } |
| throw runex; |
| } |
| } |
| |
| private static final TriConsumer<String, Object, ObjectOutputStream> SERIALIZER = |
| (k, v, objectOutputStream) -> { |
| try { |
| objectOutputStream.writeObject(k); |
| objectOutputStream.writeObject(v); |
| } catch (final IOException ioex) { |
| throw new IllegalStateException(ioex); |
| } |
| }; |
| |
| @Override |
| public String toString() { |
| final StringBuilder sb = new StringBuilder(256); |
| sb.append('{'); |
| final K myKeys[] = this.keys; |
| int pos = arraySize; |
| boolean first = true; |
| if (containsNullKey) { |
| sb.append(myKeys[pos] == this ? "(this map)" : myKeys[pos]); |
| sb.append('='); |
| sb.append(values[pos] == this ? "(this map)" : values[pos]); |
| first = false; |
| } |
| --pos; |
| for (; pos >= 0; pos--) { |
| if (myKeys[pos] != null) { |
| if (first) { |
| first = false; |
| } else { |
| sb.append(", "); |
| } |
| sb.append(myKeys[pos] == this ? "(this map)" : myKeys[pos]); |
| sb.append('='); |
| sb.append(values[pos] == this ? "(this map)" : values[pos]); |
| } |
| } |
| sb.append('}'); |
| return sb.toString(); |
| } |
| |
| private static class HashCommon { |
| private HashCommon() {} |
| |
| /** 2<sup>32</sup> · φ, φ = (√5 − 1)/2. */ |
| private static final int INT_PHI = 0x9E3779B9; |
| |
| /** The reciprocal of {@link #INT_PHI} modulo 2<sup>32</sup>. */ |
| private static final int INV_INT_PHI = 0x144cbc89; |
| |
| /** Avalanches the bits of an integer by applying the finalisation step of MurmurHash3. |
| * |
| * <p>This method implements the finalisation step of Austin Appleby's |
| * <a href="http://code.google.com/p/smhasher/">MurmurHash3</a>. |
| * Its purpose is to avalanche the bits of the argument to within 0.25% bias. |
| * |
| * @param x an integer. |
| * @return a hash value with good avalanching properties. |
| */ |
| public static int murmurHash3(int x) { |
| x ^= x >>> 16; |
| x *= 0x85ebca6b; |
| x ^= x >>> 13; |
| x *= 0xc2b2ae35; |
| x ^= x >>> 16; |
| return x; |
| } |
| |
| /** |
| * Quickly mixes the bits of an integer. |
| * |
| * <p>This method mixes the bits of the argument by multiplying by the golden ratio and |
| * xorshifting the result. It is borrowed from <a href="https://github.com/OpenHFT/Koloboke">Koloboke</a>, and |
| * it has slightly worse behaviour than {@link #murmurHash3(int)} (in open-addressing hash tables the average |
| * number of probes is slightly larger), but it's much faster. |
| * |
| * @param x an integer. |
| * @return a hash value obtained by mixing the bits of {@code x}. |
| * @see #invMix(int) |
| */ |
| public static int mix(final int x) { |
| final int h = x * INT_PHI; |
| return h ^ (h >>> 16); |
| } |
| |
| /** The inverse of {@link #mix(int)}. This method is mainly useful to create unit tests. |
| * |
| * @param x an integer. |
| * @return a value that passed through {@link #mix(int)} would give {@code x}. |
| */ |
| public static int invMix(final int x) { |
| return (x ^ x >>> 16) * INV_INT_PHI; |
| } |
| |
| /** Return the least power of two greater than or equal to the specified value. |
| * |
| * <p>Note that this function will return 1 when the argument is 0. |
| * |
| * @param x an integer smaller than or equal to 2<sup>30</sup>. |
| * @return the least power of two greater than or equal to the specified value. |
| */ |
| public static int nextPowerOfTwo(int x) { |
| if (x == 0) { |
| return 1; |
| } |
| x--; |
| x |= x >> 1; |
| x |= x >> 2; |
| x |= x >> 4; |
| x |= x >> 8; |
| return (x | x >> 16) + 1; |
| } |
| |
| /** Return the least power of two greater than or equal to the specified value. |
| * |
| * <p>Note that this function will return 1 when the argument is 0. |
| * |
| * @param x a long integer smaller than or equal to 2<sup>62</sup>. |
| * @return the least power of two greater than or equal to the specified value. |
| */ |
| public static long nextPowerOfTwo(long x) { |
| if (x == 0) { |
| return 1; |
| } |
| x--; |
| x |= x >> 1; |
| x |= x >> 2; |
| x |= x >> 4; |
| x |= x >> 8; |
| x |= x >> 16; |
| return (x | x >> 32) + 1; |
| } |
| |
| |
| /** Returns the maximum number of entries that can be filled before rehashing. |
| * |
| * @param n the size of the backing array. |
| * @param f the load factor. |
| * @return the maximum number of entries before rehashing. |
| */ |
| public static int maxFill(final int n, final float f) { |
| /* We must guarantee that there is always at least |
| * one free entry (even with pathological load factors). */ |
| return Math.min((int) Math.ceil(n * f), n - 1); |
| } |
| |
| /** |
| * Returns the least power of two smaller than or equal to 2<sup>30</sup> and larger than or equal to |
| * <code>Math.ceil( expected / f )</code>. |
| * |
| * @param expected the expected number of elements in a hash table. |
| * @param f the load factor. |
| * @return the minimum possible size for a backing array. |
| * @throws IllegalArgumentException if the necessary size is larger than 2<sup>30</sup>. |
| */ |
| public static int arraySize(final int expected, final float f) { |
| final long result = Math.max(2, nextPowerOfTwo((long) Math.ceil(expected / f))); |
| if (result > (1 << 30)) { |
| throw new IllegalArgumentException("Too large (" + expected + |
| " expected elements with load factor " + f + ")"); |
| } |
| return (int) result; |
| } |
| } |
| } |