|
|
@@ -1,106 +1,29 @@
|
|
|
/*
|
|
|
- * @author Paolo Boldi
|
|
|
- * @author Sebastiano Vigna
|
|
|
- * @author Ranides Atterwim <contact@ranides.net>
|
|
|
- *
|
|
|
- * 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.
|
|
|
+ * @author Ranides Atterwim <ranides@gmail.com>
|
|
|
+ * @copyright Ranides Atterwim
|
|
|
+ * @license WTFPL
|
|
|
+ * @url http://ranides.net/projects/assira
|
|
|
*/
|
|
|
-
|
|
|
package net.ranides.assira.collection.lookups;
|
|
|
|
|
|
-import net.ranides.assira.collection.IntCollection;
|
|
|
-import net.ranides.assira.collection.iterators.IntIterator;
|
|
|
-import net.ranides.assira.collection.sets.ASet;
|
|
|
-import net.ranides.assira.collection.arrays.ArrayUtils;
|
|
|
import net.ranides.assira.collection.utils.HashUtils;
|
|
|
import net.ranides.assira.collection.HashComparator;
|
|
|
-import java.util.ArrayList;
|
|
|
-import java.util.Iterator;
|
|
|
import java.util.Map;
|
|
|
-import java.util.NoSuchElementException;
|
|
|
-import java.util.Set;
|
|
|
import net.ranides.assira.collection.utils.HashCollection;
|
|
|
-import net.ranides.assira.math.MathUtils;
|
|
|
|
|
|
/**
|
|
|
- * A type-specific hash map with a fast, small-footprint implementation whose
|
|
|
- * {@linkplain net.ranides.assira.collection.HashComparator hashing strategy} is
|
|
|
- * specified at creation time.
|
|
|
- *
|
|
|
- * <P>Instances of this class use a hash table to represent a map. The table is
|
|
|
- * enlarged as needed by doubling its size when new entries are created, but it
|
|
|
- * is <em>never</em> made smaller (even on a {@link #clear()}). A family of {@linkplain #trim() trimming
|
|
|
- * methods} lets you control the size of the table; this is particularly useful
|
|
|
- * if you reuse instances of this class.
|
|
|
- *
|
|
|
- * @todo (assira # 6) review.hash.custom
|
|
|
+ * A type-specific hash map with primitive int values, whose
|
|
|
+ * {@linkplain HashComparator hashing strategy} is specified at creation time.
|
|
|
*/
|
|
|
@SuppressWarnings({
|
|
|
"PMD.AvoidReassigningParameters",
|
|
|
"PMD.OverrideBothEqualsAndHashcode",
|
|
|
"EqualsAndHashcode"
|
|
|
})
|
|
|
-public final class CustomLookup<K> extends Lookup<K> implements java.io.Serializable {
|
|
|
+public final class CustomLookup<K> extends AHashLookup<K> {
|
|
|
|
|
|
- private static final long serialVersionUID = 0L;
|
|
|
- /**
|
|
|
- * The array of keys.
|
|
|
- */
|
|
|
- protected transient K[] akeys;
|
|
|
- /**
|
|
|
- * The array of values.
|
|
|
- */
|
|
|
- protected transient int[] avalues;
|
|
|
- /**
|
|
|
- * The array telling whether a position is used.
|
|
|
- */
|
|
|
- protected transient boolean used[];
|
|
|
- /**
|
|
|
- * The acceptable load factor.
|
|
|
- */
|
|
|
- protected float f;
|
|
|
- /**
|
|
|
- * The current table size.
|
|
|
- */
|
|
|
- protected transient int n;
|
|
|
- /**
|
|
|
- * Threshold after which we rehash. It must be the table size times
|
|
|
- * {@link #f}.
|
|
|
- */
|
|
|
- protected transient int maxFill;
|
|
|
- /**
|
|
|
- * The mask for wrapping a position counter.
|
|
|
- */
|
|
|
- protected transient int mask;
|
|
|
- /**
|
|
|
- * Number of entries in the set.
|
|
|
- */
|
|
|
- protected int size;
|
|
|
- /**
|
|
|
- * Cached set of entries.
|
|
|
- */
|
|
|
- protected transient volatile Set<LookupEntry<K>> entries;
|
|
|
- /**
|
|
|
- * Cached set of keys.
|
|
|
- */
|
|
|
- protected transient volatile Set<K> keys;
|
|
|
- /**
|
|
|
- * Cached collection of values.
|
|
|
- */
|
|
|
- protected transient volatile IntCollection values;
|
|
|
- /**
|
|
|
- * The hash strategy of this custom map.
|
|
|
- */
|
|
|
+ private static final long serialVersionUID = 2L;
|
|
|
+
|
|
|
protected HashComparator<K> strategy;
|
|
|
|
|
|
/**
|
|
|
@@ -111,25 +34,13 @@ public final class CustomLookup<K> extends Lookup<K> implements java.io.Serializ
|
|
|
* <code>f</code>.
|
|
|
*
|
|
|
* @param expected the expected number of elements in the hash set.
|
|
|
- * @param f the load factor.
|
|
|
+ * @param factor the load factor.
|
|
|
* @param strategy the strategy.
|
|
|
*/
|
|
|
@SuppressWarnings("unchecked")
|
|
|
- public CustomLookup(int expected, float f, HashComparator<K> strategy) {
|
|
|
+ public CustomLookup(int expected, float factor, HashComparator<K> strategy) {
|
|
|
+ super(expected, factor);
|
|
|
this.strategy = strategy;
|
|
|
- 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.f = f;
|
|
|
- n = HashCollection.arraysize(expected, f);
|
|
|
- mask = n - 1;
|
|
|
- maxFill = HashCollection.arrayfill(n, f);
|
|
|
- akeys = (K[]) new Object[n];
|
|
|
- avalues = new int[n];
|
|
|
- used = new boolean[n];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -242,820 +153,15 @@ public final class CustomLookup<K> extends Lookup<K> implements java.io.Serializ
|
|
|
public HashComparator<K> strategy() {
|
|
|
return strategy;
|
|
|
}
|
|
|
- /*
|
|
|
- * The following methods implements some basic building blocks used by
|
|
|
- * all accessors. They are (and should be maintained) identical to those used in OpenHashSet.drv.
|
|
|
- */
|
|
|
-
|
|
|
- @Override
|
|
|
- public int put(K key, int value) {
|
|
|
- // The starting point.
|
|
|
- int pos = HashUtils.murmurHash3(strategy.hashCode(key)) & mask;
|
|
|
- // There's always an unused entry.
|
|
|
- while (used[ pos]) {
|
|
|
- if (strategy.equals(akeys[pos], key)) {
|
|
|
- int oldValue = avalues[pos];
|
|
|
- avalues[ pos] = value;
|
|
|
- return oldValue;
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- used[ pos] = true;
|
|
|
- akeys[ pos] = key;
|
|
|
- avalues[ pos] = value;
|
|
|
- if (++size >= maxFill) {
|
|
|
- rehash(HashCollection.arraysize(size + 1, f));
|
|
|
- }
|
|
|
- return defRetValue;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Integer put(K key, Integer value) {
|
|
|
- int v = value;
|
|
|
- // The starting point.
|
|
|
- int pos = HashUtils.murmurHash3(strategy.hashCode(key)) & mask;
|
|
|
- // There's always an unused entry.
|
|
|
- while (used[ pos]) {
|
|
|
- if (strategy.equals(akeys[ pos], key)) {
|
|
|
- Integer oldValue = avalues[pos];
|
|
|
- avalues[pos] = v;
|
|
|
- return oldValue;
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- used[ pos] = true;
|
|
|
- akeys[ pos] = key;
|
|
|
- avalues[ pos] = v;
|
|
|
- if (++size >= maxFill) {
|
|
|
- rehash(HashCollection.arraysize(size + 1, f));
|
|
|
- }
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Adds an increment to avalues currently associated with a akeys.
|
|
|
- *
|
|
|
- * <P>Note that this method respects the
|
|
|
- * {@link #defaultReturnValue() default return avalues} semantics: when
|
|
|
- called with a akeys that does not currently appears in the map, the akeys
|
|
|
- will be associated with the default return avalues plus the given
|
|
|
- increment.
|
|
|
- *
|
|
|
- * @param key the akeys.
|
|
|
- * @param delta the increment.
|
|
|
- * @return the old avalues, or the
|
|
|
- {@link #defaultReturnValue() default return avalues} if no avalues was
|
|
|
- present for the given akeys.
|
|
|
- */
|
|
|
- public int addTo(K key, int delta) {
|
|
|
- // The starting point.
|
|
|
- int pos = (HashUtils.murmurHash3(strategy.hashCode((K) (key)))) & mask;
|
|
|
- // There's always an unused entry.
|
|
|
- while (used[ pos]) {
|
|
|
- if (strategy.equals(akeys[ pos], key)) {
|
|
|
- int oldValue = avalues[ pos];
|
|
|
- avalues[ pos] += delta;
|
|
|
- return oldValue;
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- used[ pos] = true;
|
|
|
- akeys[ pos] = key;
|
|
|
- avalues[ pos] = defRetValue + delta;
|
|
|
- if (++size >= maxFill) {
|
|
|
- rehash(HashCollection.arraysize(size + 1, f));
|
|
|
- }
|
|
|
- return defRetValue;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Shifts left entries with the specified hash code, starting at the
|
|
|
- * specified position, and empties the resulting free entry.
|
|
|
- *
|
|
|
- * @param pos a starting position.
|
|
|
- * @return the position cleared by the shifting process.
|
|
|
- */
|
|
|
- private int shiftKeys(int pos) {
|
|
|
- // Shift entries with the same hash.
|
|
|
- int last, slot;
|
|
|
- for (;;) {
|
|
|
- pos = ((last = pos) + 1) & mask;
|
|
|
- while (used[ pos]) {
|
|
|
- slot = (HashUtils.murmurHash3(strategy.hashCode((K) (akeys[ pos])))) & mask;
|
|
|
- if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos) {
|
|
|
- break;
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- if (!used[ pos]) {
|
|
|
- break;
|
|
|
- }
|
|
|
- akeys[ last] = akeys[ pos];
|
|
|
- avalues[ last] = avalues[ pos];
|
|
|
- }
|
|
|
- used[ last] = false;
|
|
|
- akeys[ last] = null;
|
|
|
- return last;
|
|
|
- }
|
|
|
-
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- @Override
|
|
|
- public int removeInt(Object key) {
|
|
|
- // The starting point.
|
|
|
- int pos = HashUtils.murmurHash3(strategy.hashCode((K)key)) & mask;
|
|
|
- // There's always an unused entry.
|
|
|
- while (used[ pos]) {
|
|
|
- if (strategy.equals(akeys[ pos], (K)key)) {
|
|
|
- size--;
|
|
|
- int v = avalues[ pos];
|
|
|
- shiftKeys(pos);
|
|
|
- return v;
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- return defRetValue;
|
|
|
- }
|
|
|
-
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- @Override
|
|
|
- public Integer remove(Object key) {
|
|
|
- K kkey = (K)key;
|
|
|
- // The starting point.
|
|
|
- int pos = (HashUtils.murmurHash3(strategy.hashCode((K) (kkey)))) & mask;
|
|
|
- // There's always an unused entry.
|
|
|
- while (used[ pos]) {
|
|
|
- if (strategy.equals(akeys[pos], kkey)) {
|
|
|
- size--;
|
|
|
- int v = avalues[ pos];
|
|
|
- shiftKeys(pos);
|
|
|
- return v;
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- @Override
|
|
|
- public int getInt(Object key) {
|
|
|
- // The starting point.
|
|
|
- int pos = HashUtils.murmurHash3(strategy.hashCode((K)key)) & mask;
|
|
|
- // There's always an unused entry.
|
|
|
- while (used[ pos]) {
|
|
|
- if (strategy.equals(akeys[pos], (K)key)) {
|
|
|
- return avalues[pos];
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- return defRetValue;
|
|
|
- }
|
|
|
-
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- @Override
|
|
|
- public boolean containsKey(Object key) {
|
|
|
- // The starting point.
|
|
|
- int pos = HashUtils.murmurHash3(strategy.hashCode((K)key)) & mask;
|
|
|
- // There's always an unused entry.
|
|
|
- while (used[ pos]) {
|
|
|
- if (strategy.equals(akeys[pos], (K)key)) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public boolean containsValue(int v) {
|
|
|
- int nvalues[] = this.avalues;
|
|
|
- boolean nused[] = this.used;
|
|
|
- for (int i = n; i-- != 0;) {
|
|
|
- if (nused[i] && (nvalues[i] == v)) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- }
|
|
|
- return false;
|
|
|
- }
|
|
|
- /* 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;
|
|
|
- }
|
|
|
- size = 0;
|
|
|
- ArrayUtils.fill(used, false);
|
|
|
- // We null all object entries so that the garbage collector can do its work.
|
|
|
- ArrayUtils.fill(akeys, null);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public int size() {
|
|
|
- return size;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public boolean isEmpty() {
|
|
|
- return size == 0;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * The entry class for a hash map does not record akeys and avalues, but rather
|
|
|
- * the position in the hash table of the corresponding entry. This is
|
|
|
- * necessary so that calls to {@link java.util.Map.Entry#setValue(Object)}
|
|
|
- * are reflected in the map
|
|
|
- */
|
|
|
- private final class MapEntry implements LookupEntry<K>, Map.Entry<K, Integer> {
|
|
|
- // The table index this entry refers to, or -1 if this entry has been deleted.
|
|
|
-
|
|
|
- int index;
|
|
|
-
|
|
|
- MapEntry(int index) {
|
|
|
- this.index = index;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public K getKey() {
|
|
|
- return akeys[ index];
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Integer getValue() {
|
|
|
- return avalues[ index];
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public int getIntValue() {
|
|
|
- return avalues[ index];
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public int setValue(int v) {
|
|
|
- int oldValue = avalues[ index];
|
|
|
- avalues[ index] = v;
|
|
|
- return oldValue;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Integer setValue(Integer value) {
|
|
|
- return setValue(value.intValue());
|
|
|
- }
|
|
|
-
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- @Override
|
|
|
- public boolean equals(Object object) {
|
|
|
- if (!(object instanceof Map.Entry)) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- Map.Entry<K, Integer> entry = (Map.Entry<K, Integer>) object;
|
|
|
- K key = entry.getKey();
|
|
|
- int v = entry.getValue();
|
|
|
- return strategy.equals(akeys[index], key) && (avalues[index] == v);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public int hashCode() {
|
|
|
- return (strategy.hashCode((K) (akeys[ index]))) ^ (avalues[ index]);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public String toString() {
|
|
|
- return akeys[ index] + "=>" + avalues[ index];
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * An iterator over a hash map.
|
|
|
- */
|
|
|
- private class MapIterator {
|
|
|
-
|
|
|
- /**
|
|
|
- * The index of the next entry to be returned, if positive or zero. If
|
|
|
- * negative, the next entry to be returned, if any, is that of index
|
|
|
- * -pos -2 from the {@link #wrapped} list.
|
|
|
- */
|
|
|
- int pos;
|
|
|
- /**
|
|
|
- * The index of the last entry that has been returned. It is -1 if
|
|
|
- * either we did not return an entry yet, or the last returned entry has
|
|
|
- * been removed.
|
|
|
- */
|
|
|
- int last;
|
|
|
- /**
|
|
|
- * A downward counter measuring how many entries must still be returned.
|
|
|
- */
|
|
|
- int counter;
|
|
|
- /**
|
|
|
- * A lazily allocated list containing the keys of elements that have
|
|
|
- * wrapped around the table because of removals; such elements would not
|
|
|
- * be enumerated (other elements would be usually enumerated twice in
|
|
|
- * their place).
|
|
|
- */
|
|
|
- ArrayList<K> wrapped;
|
|
|
-
|
|
|
- public MapIterator() {
|
|
|
- this.pos = CustomLookup.this.n;
|
|
|
- this.last = -1;
|
|
|
- this.counter = size;
|
|
|
- this.wrapped = null;
|
|
|
-
|
|
|
- boolean used[] = CustomLookup.this.used;
|
|
|
- if (counter != 0) {
|
|
|
- while (!used[ --pos]) { } // NOPMD
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public boolean hasNext() {
|
|
|
- return counter != 0;
|
|
|
- }
|
|
|
-
|
|
|
- public int nextEntry() {
|
|
|
- if (!hasNext()) {
|
|
|
- throw new NoSuchElementException();
|
|
|
- }
|
|
|
- counter--;
|
|
|
- // We are just enumerating elements from the wrapped list.
|
|
|
- if (pos < 0) {
|
|
|
- K ckey = wrapped.get(-(last = --pos) - 2);
|
|
|
- // The starting point.
|
|
|
- int cpos = HashUtils.murmurHash3(strategy.hashCode(ckey)) & mask;
|
|
|
- // There's always an unused entry.
|
|
|
- while (used[ cpos]) {
|
|
|
- if (strategy.equals(akeys[ cpos], ckey)) {
|
|
|
- return cpos;
|
|
|
- }
|
|
|
- cpos = (cpos + 1) & mask;
|
|
|
- }
|
|
|
- }
|
|
|
- last = pos;
|
|
|
- //System.err.println( "Count: " + c );
|
|
|
- if (counter != 0) {
|
|
|
- boolean used[] = CustomLookup.this.used;
|
|
|
- while (pos-- != 0 && !used[ pos]) { } // NOPMD
|
|
|
- // When here pos < 0 there are no more elements to be enumerated by scanning, but wrapped might be nonempty.
|
|
|
- }
|
|
|
- return last;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Shifts left entries with the specified hash code, starting at the
|
|
|
- * specified position, and empties the resulting free entry. If any
|
|
|
- * entry wraps around the table, instantiates lazily {@link #wrapped}
|
|
|
- * and stores the entry akeys.
|
|
|
- *
|
|
|
- * @param pos a starting position.
|
|
|
- * @return the position cleared by the shifting process.
|
|
|
- */
|
|
|
- protected final int shiftKeys(int pos) {
|
|
|
- // Shift entries with the same hash.
|
|
|
- int ilast, islot;
|
|
|
- for (;;) {
|
|
|
- pos = ((ilast = pos) + 1) & mask;
|
|
|
- while (used[ pos]) {
|
|
|
- islot = (HashUtils.murmurHash3(strategy.hashCode((K) (akeys[ pos])))) & mask;
|
|
|
- if (ilast <= pos ? ilast >= islot || islot > pos : ilast >= islot && islot > pos) {
|
|
|
- break;
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- if (!used[ pos]) {
|
|
|
- break;
|
|
|
- }
|
|
|
- if (pos < ilast) {
|
|
|
- // Wrapped entry.
|
|
|
- if (wrapped == null) {
|
|
|
- wrapped = new ArrayList<>();
|
|
|
- }
|
|
|
- wrapped.add(akeys[ pos]);
|
|
|
- }
|
|
|
- akeys[ ilast] = akeys[ pos];
|
|
|
- avalues[ ilast] = avalues[ pos];
|
|
|
- }
|
|
|
- used[ ilast] = false;
|
|
|
- akeys[ ilast] = null;
|
|
|
- return ilast;
|
|
|
- }
|
|
|
-
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- public void remove() {
|
|
|
- if (last == -1) {
|
|
|
- throw new IllegalStateException();
|
|
|
- }
|
|
|
- if (pos < -1) {
|
|
|
- // We're removing wrapped entries.
|
|
|
- CustomLookup.this.remove(wrapped.set(-pos - 2, null));
|
|
|
- last = -1;
|
|
|
- return;
|
|
|
- }
|
|
|
- size--;
|
|
|
- if (shiftKeys(last) == pos && counter > 0) {
|
|
|
- counter++;
|
|
|
- nextEntry();
|
|
|
- }
|
|
|
- last = -1; // You can no longer remove this entry.
|
|
|
- }
|
|
|
-
|
|
|
- public int skip(int n) {
|
|
|
- int i = n;
|
|
|
- while (i-- != 0 && hasNext()) {
|
|
|
- nextEntry();
|
|
|
- }
|
|
|
- return n - i - 1;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private class EntryIterator extends MapIterator implements Iterator<LookupEntry<K>> {
|
|
|
-
|
|
|
- private MapEntry entry;
|
|
|
-
|
|
|
- public EntryIterator() { }
|
|
|
-
|
|
|
- @Override
|
|
|
- public LookupEntry<K> next() {
|
|
|
- return entry = new MapEntry(nextEntry());
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void remove() {
|
|
|
- super.remove();
|
|
|
- entry.index = -1; // You cannot use a deleted entry.
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private class FastEntryIterator extends MapIterator implements Iterator<LookupEntry<K>> {
|
|
|
-
|
|
|
- final BasicEntry<K> entry = new BasicEntry<>(null, 0);
|
|
|
-
|
|
|
- public FastEntryIterator() { }
|
|
|
-
|
|
|
- @Override
|
|
|
- public BasicEntry<K> next() {
|
|
|
- int e = nextEntry();
|
|
|
- entry.key = akeys[ e];
|
|
|
- entry.value = avalues[ e];
|
|
|
- return entry;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private final class MapEntrySet extends ASet<LookupEntry<K>> {
|
|
|
-
|
|
|
- @Override
|
|
|
- public Iterator<LookupEntry<K>> iterator() {
|
|
|
- return new EntryIterator();
|
|
|
- }
|
|
|
-
|
|
|
- public Iterator<LookupEntry<K>> fastIterator() {
|
|
|
- return new FastEntryIterator();
|
|
|
- }
|
|
|
-
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- @Override
|
|
|
- public boolean contains(Object object) {
|
|
|
- if (!(object instanceof Map.Entry)) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- Map.Entry<K, Integer> entry = (Map.Entry<K, Integer>) object;
|
|
|
- K ckey = entry.getKey();
|
|
|
- // The starting point.
|
|
|
- int pos = (HashUtils.murmurHash3(strategy.hashCode((K) (ckey)))) & mask;
|
|
|
- // There's always an unused entry.
|
|
|
- while (used[ pos]) {
|
|
|
- if (strategy.equals(akeys[ pos], ckey)) {
|
|
|
- int v = entry.getValue();
|
|
|
- return avalues[pos] == v;
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- @Override
|
|
|
- public boolean remove(Object object) {
|
|
|
- if (!(object instanceof Map.Entry)) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- Map.Entry<K, Integer> entry = (Map.Entry<K, Integer>) object;
|
|
|
- K ckey = entry.getKey();
|
|
|
- // The starting point.
|
|
|
- int pos = (HashUtils.murmurHash3(strategy.hashCode((K) (ckey)))) & mask;
|
|
|
- // There's always an unused entry.
|
|
|
- while (used[ pos]) {
|
|
|
- if (strategy.equals(akeys[pos], (K)ckey)) {
|
|
|
- CustomLookup.this.remove(entry.getKey());
|
|
|
- return true;
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public int size() {
|
|
|
- return size;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void clear() {
|
|
|
- CustomLookup.this.clear();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Set<LookupEntry<K>> fastEntrySet() {
|
|
|
- if (entries == null) {
|
|
|
- entries = new MapEntrySet();
|
|
|
- }
|
|
|
- return entries;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * An iterator on keys.
|
|
|
- *
|
|
|
- * <P>We simply override the
|
|
|
- * {@link java.util.ListIterator#next()}/{@link java.util.ListIterator#previous()}
|
|
|
- * methods (and possibly their type-specific counterparts) so that they
|
|
|
- * return keys instead of entries.
|
|
|
- */
|
|
|
- private final class KeyIterator extends MapIterator implements Iterator<K> {
|
|
|
-
|
|
|
- public KeyIterator() {
|
|
|
- super();
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public K next() {
|
|
|
- return akeys[ nextEntry()];
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private final class KeySet extends ASet<K> {
|
|
|
-
|
|
|
- @Override
|
|
|
- public Iterator<K> iterator() {
|
|
|
- return new KeyIterator();
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public int size() {
|
|
|
- return size;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- @SuppressWarnings("element-type-mismatch")
|
|
|
- public boolean contains(Object key) {
|
|
|
- return containsKey(key);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- @SuppressWarnings("element-type-mismatch")
|
|
|
- public boolean remove(Object key) {
|
|
|
- int oldSize = size;
|
|
|
- CustomLookup.this.remove(key);
|
|
|
- return size != oldSize;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void clear() {
|
|
|
- CustomLookup.this.clear();
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
@Override
|
|
|
- public Set<K> keySet() {
|
|
|
- if (keys == null) {
|
|
|
- keys = new KeySet();
|
|
|
- }
|
|
|
- return keys;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * An iterator on values.
|
|
|
- *
|
|
|
- * <P>We simply override the
|
|
|
- * {@link java.util.ListIterator#next()}/{@link java.util.ListIterator#previous()}
|
|
|
- * methods (and possibly their type-specific counterparts) so that they
|
|
|
- * return values instead of entries.
|
|
|
- */
|
|
|
- private final class ValueIterator extends IntIterator {
|
|
|
-
|
|
|
- private final MapIterator delegate = new MapIterator();
|
|
|
-
|
|
|
- public ValueIterator() { }
|
|
|
-
|
|
|
- @Override
|
|
|
- public int nextInt() {
|
|
|
- return avalues[delegate.nextEntry()];
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Integer next() {
|
|
|
- return avalues[delegate.nextEntry()];
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public boolean hasNext() {
|
|
|
- return delegate.hasNext();
|
|
|
- }
|
|
|
+ protected int hashkey(K key) {
|
|
|
+ return HashUtils.murmurHash3(strategy.hashCode(key));
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public IntCollection values() {
|
|
|
- if (values == null) {
|
|
|
- values = new IntCollection() {
|
|
|
- @Override
|
|
|
- public IntIterator iterator() {
|
|
|
- return new ValueIterator();
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public int size() {
|
|
|
- return size;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public boolean contains(int v) {
|
|
|
- return containsValue(v);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void clear() {
|
|
|
- CustomLookup.this.clear();
|
|
|
- }
|
|
|
- };
|
|
|
- }
|
|
|
- return values;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Rehashes the map, making the table as small as possible.
|
|
|
- *
|
|
|
- * <P>This method rehashes the table to the smallest size satisfying the
|
|
|
- * load factor. It can be used when the set will not be changed anymore, so
|
|
|
- * to optimize access speed and size.
|
|
|
- *
|
|
|
- * <P>If the table size is already the minimum possible, this method does
|
|
|
- * nothing.
|
|
|
- *
|
|
|
- * @return true if there was enough memory to trim the map.
|
|
|
- * @see #trim(int)
|
|
|
- */
|
|
|
- public boolean trim() {
|
|
|
- int l = HashCollection.arraysize(size, f);
|
|
|
- if (l >= n) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- try {
|
|
|
- rehash(l);
|
|
|
- } catch (OutOfMemoryError cantDoIt) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 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. null null null null null {@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.
|
|
|
- * @see #trim()
|
|
|
- */
|
|
|
- public boolean trim(int n) {
|
|
|
- int l = MathUtils.pow2next((int) Math.ceil(n / f));
|
|
|
- if (this.n <= l) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- try {
|
|
|
- rehash(l);
|
|
|
- } catch (OutOfMemoryError cantDoIt) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Resizes 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(int newN) {
|
|
|
- int i = 0, pos;
|
|
|
- boolean nused[] = this.used;
|
|
|
- K ckey;
|
|
|
- K ikeys[] = this.akeys;
|
|
|
- int ivalues[] = this.avalues;
|
|
|
- int newMask = newN - 1;
|
|
|
- K newKey[] = (K[]) new Object[newN];
|
|
|
- int newValue[] = new int[newN];
|
|
|
- boolean newUsed[] = new boolean[newN];
|
|
|
- for (int j = size; j-- != 0;) {
|
|
|
- while (!nused[ i]) {
|
|
|
- i++;
|
|
|
- }
|
|
|
- ckey = ikeys[ i];
|
|
|
- pos = (HashUtils.murmurHash3(strategy.hashCode((K) (ckey)))) & newMask;
|
|
|
- while (newUsed[ pos]) {
|
|
|
- pos = (pos + 1) & newMask;
|
|
|
- }
|
|
|
- newUsed[ pos] = true;
|
|
|
- newKey[ pos] = ckey;
|
|
|
- newValue[ pos] = ivalues[ i];
|
|
|
- i++;
|
|
|
- }
|
|
|
- n = newN;
|
|
|
- mask = newMask;
|
|
|
- maxFill = HashCollection.arrayfill(n, f);
|
|
|
- this.akeys = newKey;
|
|
|
- this.avalues = newValue;
|
|
|
- this.used = newUsed;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Returns a hash code for this map.
|
|
|
- *
|
|
|
- * This method overrides the generic method provided by the superclass.
|
|
|
- * Since
|
|
|
- * <code>equals()</code> is not overriden, it is important that the avalues
|
|
|
- * returned by this method is the same avalues as the one returned by the
|
|
|
- * overriden method.
|
|
|
- *
|
|
|
- * @return a hash code for this map.
|
|
|
- */
|
|
|
- @Override
|
|
|
- public int hashCode() {
|
|
|
- int h = 0;
|
|
|
- for (int j = size, i = 0, t = 0; j-- != 0;) {
|
|
|
- while (!used[ i]) {
|
|
|
- i++;
|
|
|
- }
|
|
|
- if (this != akeys[ i]) {
|
|
|
- t = strategy.hashCode((K)akeys[i]);
|
|
|
- }
|
|
|
- t ^= avalues[i];
|
|
|
- h += t;
|
|
|
- i++;
|
|
|
- }
|
|
|
- return h;
|
|
|
- }
|
|
|
-
|
|
|
- private void writeObject(java.io.ObjectOutputStream ostream) throws java.io.IOException {
|
|
|
- K ikeys[] = this.akeys;
|
|
|
- int ivalues[] = this.avalues;
|
|
|
- MapIterator iterator = new MapIterator();
|
|
|
- ostream.defaultWriteObject();
|
|
|
- for (int j = size, element; j-- != 0;) {
|
|
|
- element = iterator.nextEntry();
|
|
|
- ostream.writeObject(ikeys[ element]);
|
|
|
- ostream.writeInt(ivalues[ element]);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @SuppressWarnings({
|
|
|
- "unchecked",
|
|
|
- "MismatchedReadAndWriteOfArray"
|
|
|
- })
|
|
|
- private void readObject(java.io.ObjectInputStream istream) throws java.io.IOException, ClassNotFoundException {
|
|
|
- istream.defaultReadObject();
|
|
|
- n = HashCollection.arraysize(size, f);
|
|
|
- maxFill = HashCollection.arrayfill(n, f);
|
|
|
- mask = n - 1;
|
|
|
- K ikeys[] = this.akeys = (K[]) new Object[n];
|
|
|
- int ivalues[] = this.avalues = new int[n];
|
|
|
- boolean iused[] = this.used = new boolean[n];
|
|
|
- for (int i = size, pos; i-- != 0;) {
|
|
|
- K ckey = (K) istream.readObject();
|
|
|
- int cval = istream.readInt();
|
|
|
- pos = (HashUtils.murmurHash3(strategy.hashCode((K) (ckey)))) & mask;
|
|
|
- while (iused[ pos]) {
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- iused[pos] = true;
|
|
|
- ikeys[pos] = ckey;
|
|
|
- ivalues[pos] = cval;
|
|
|
- }
|
|
|
+ protected boolean cmpkey(K key1, K key2) {
|
|
|
+ return strategy.equals(key1, key2);
|
|
|
}
|
|
|
|
|
|
}
|