|
|
@@ -17,16 +17,13 @@ import java.util.Map;
|
|
|
import java.util.NoSuchElementException;
|
|
|
import java.util.Set;
|
|
|
import net.ranides.assira.collection.utils.HashCollection;
|
|
|
+import net.ranides.assira.generic.ValueUtils;
|
|
|
import net.ranides.assira.math.MathUtils;
|
|
|
|
|
|
/**
|
|
|
* A type-specific hash map with primitive int values.
|
|
|
- * @todo (assira # 2) review.hash.map V=int
|
|
|
+ * @todo (assira # 8) review.hash.map V=int
|
|
|
*/
|
|
|
-@SuppressWarnings({
|
|
|
- "PMD.AvoidReassigningParameters",
|
|
|
- "EqualsAndHashcode"
|
|
|
-})
|
|
|
public abstract class AHashLookup<K> extends Lookup<K> implements java.io.Serializable {
|
|
|
|
|
|
private static final long serialVersionUID = 4L;
|
|
|
@@ -54,7 +51,7 @@ public abstract class AHashLookup<K> extends Lookup<K> implements java.io.Serial
|
|
|
* Threshold after which we rehash. It must be the table size times
|
|
|
* {@link #factor}.
|
|
|
*/
|
|
|
- protected transient int maxFill;
|
|
|
+ protected transient int capacity;
|
|
|
/**
|
|
|
* The mask for wrapping a position counter.
|
|
|
*/
|
|
|
@@ -98,192 +95,52 @@ public abstract class AHashLookup<K> extends Lookup<K> implements java.io.Serial
|
|
|
this.factor = f;
|
|
|
n = HashCollection.arraysize(expected, f);
|
|
|
mask = n - 1;
|
|
|
- maxFill = HashCollection.factorsize(n, f);
|
|
|
+ capacity = HashCollection.factorsize(n, f);
|
|
|
keys = (K[]) new Object[n];
|
|
|
values = new int[n];
|
|
|
used = new boolean[n];
|
|
|
}
|
|
|
|
|
|
- protected abstract int hashkey(K key);
|
|
|
+ protected abstract int hash(K key);
|
|
|
|
|
|
- protected abstract boolean cmpkey(K key1, K key2);
|
|
|
+ protected abstract boolean compare(K key1, K key2);
|
|
|
|
|
|
@Override
|
|
|
public final int put(K key, int value) {
|
|
|
- // The starting point.
|
|
|
- int pos = HashUtils.murmurHash3(hashkey(key)) & mask;
|
|
|
- // There's always an unused entry.
|
|
|
- while (used[ pos]) {
|
|
|
- if (cmpkey(keys[pos], key)) {
|
|
|
- int oldValue = values[pos];
|
|
|
- values[ pos] = value;
|
|
|
- return oldValue;
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- used[ pos] = true;
|
|
|
- keys[ pos] = key;
|
|
|
- values[ pos] = value;
|
|
|
- if (++size >= maxFill) {
|
|
|
- rehash(HashCollection.arraysize(size + 1, factor));
|
|
|
- }
|
|
|
- return defRetValue;
|
|
|
+ int pos = ifind(key);
|
|
|
+ return used[pos] ? ireplace(pos, value) : iinsert(pos, key, value);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public final Integer put(K key, Integer value) {
|
|
|
- int v = value;
|
|
|
- // The starting point.
|
|
|
- int pos = HashUtils.murmurHash3(hashkey(key)) & mask;
|
|
|
- // There's always an unused entry.
|
|
|
- while (used[ pos]) {
|
|
|
- if (cmpkey(keys[ pos], key)) {
|
|
|
- Integer oldValue = values[pos];
|
|
|
- values[pos] = v;
|
|
|
- return oldValue;
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- used[ pos] = true;
|
|
|
- keys[ pos] = key;
|
|
|
- values[ pos] = v;
|
|
|
- if (++size >= maxFill) {
|
|
|
- rehash(HashCollection.arraysize(size + 1, factor));
|
|
|
- }
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Adds an increment to values currently associated with a keys.
|
|
|
- *
|
|
|
- * <P>Note that this method respects the
|
|
|
- * {@link #defaultReturnValue() default return values} semantics: when
|
|
|
- called with a keys that does not currently appears in the map, the keys
|
|
|
- will be associated with the default return values plus the given
|
|
|
- increment.
|
|
|
- *
|
|
|
- * @param key the keys.
|
|
|
- * @param delta the increment.
|
|
|
- * @return the old values, or the
|
|
|
- {@link #defaultReturnValue() default return values} if no values was
|
|
|
- present for the given keys.
|
|
|
- */
|
|
|
- public final int addTo(K key, int delta) {
|
|
|
- // The starting point.
|
|
|
- int pos = HashUtils.murmurHash3(hashkey((K)key)) & mask;
|
|
|
- // There's always an unused entry.
|
|
|
- while (used[ pos]) {
|
|
|
- if (cmpkey(keys[ pos], key)) {
|
|
|
- int oldValue = values[ pos];
|
|
|
- values[ pos] += delta;
|
|
|
- return oldValue;
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- used[ pos] = true;
|
|
|
- keys[ pos] = key;
|
|
|
- values[ pos] = defRetValue + delta;
|
|
|
- if (++size >= maxFill) {
|
|
|
- rehash(HashCollection.arraysize(size + 1, factor));
|
|
|
- }
|
|
|
- return defRetValue;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Shifts left mentries 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 final int shiftKeys(int pos) {
|
|
|
- // Shift mentries with the same hash.
|
|
|
- int last, slot;
|
|
|
- for (;;) {
|
|
|
- pos = ((last = pos) + 1) & mask;
|
|
|
- while (used[ pos]) {
|
|
|
- slot = HashUtils.murmurHash3(hashkey((K)keys[ pos])) & mask;
|
|
|
- if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos) {
|
|
|
- break;
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- if (!used[ pos]) {
|
|
|
- break;
|
|
|
- }
|
|
|
- keys[ last] = keys[ pos];
|
|
|
- values[ last] = values[ pos];
|
|
|
- }
|
|
|
- used[ last] = false;
|
|
|
- keys[ last] = null;
|
|
|
- return last;
|
|
|
+ return put(key, value.intValue());
|
|
|
}
|
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
@Override
|
|
|
public final int removeInt(Object key) {
|
|
|
- // The starting point.
|
|
|
- int pos = HashUtils.murmurHash3(hashkey((K)key)) & mask;
|
|
|
- // There's always an unused entry.
|
|
|
- while (used[ pos]) {
|
|
|
- if (cmpkey(keys[ pos], (K)key)) {
|
|
|
- size--;
|
|
|
- int v = values[ pos];
|
|
|
- shiftKeys(pos);
|
|
|
- return v;
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- return defRetValue;
|
|
|
+ int pos = ifind((K)key);
|
|
|
+ return used[pos] ? ishift(pos) : defRetValue;
|
|
|
}
|
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
@Override
|
|
|
public final Integer remove(Object key) {
|
|
|
- K kkey = (K)key;
|
|
|
- // The starting point.
|
|
|
- int pos = HashUtils.murmurHash3(hashkey((K)kkey)) & mask;
|
|
|
- // There's always an unused entry.
|
|
|
- while (used[ pos]) {
|
|
|
- if (cmpkey(keys[pos], kkey)) {
|
|
|
- size--;
|
|
|
- int v = values[ pos];
|
|
|
- shiftKeys(pos);
|
|
|
- return v;
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- return null;
|
|
|
+ int pos = ifind((K)key);
|
|
|
+ return used[pos] ? ishift(pos) : defRetValue;
|
|
|
}
|
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
@Override
|
|
|
public final int getInt(Object key) {
|
|
|
- // The starting point.
|
|
|
- int pos = HashUtils.murmurHash3(hashkey((K)key)) & mask;
|
|
|
- // There's always an unused entry.
|
|
|
- while (used[ pos]) {
|
|
|
- if (cmpkey(keys[pos], (K)key)) {
|
|
|
- return values[pos];
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- return defRetValue;
|
|
|
+ int pos = ifind((K)key);
|
|
|
+ return used[pos] ? values[pos] : defRetValue;
|
|
|
}
|
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
@Override
|
|
|
public boolean containsKey(Object key) {
|
|
|
- // The starting point.
|
|
|
- int pos = HashUtils.murmurHash3(hashkey((K)key)) & mask;
|
|
|
- // There's always an unused entry.
|
|
|
- while (used[ pos]) {
|
|
|
- if (cmpkey(keys[pos], (K)key)) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- return false;
|
|
|
+ return used[ifind((K)key)];
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@@ -297,12 +154,6 @@ public abstract class AHashLookup<K> extends Lookup<K> implements java.io.Serial
|
|
|
}
|
|
|
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 final void clear() {
|
|
|
@@ -311,7 +162,6 @@ public abstract class AHashLookup<K> extends Lookup<K> implements java.io.Serial
|
|
|
}
|
|
|
size = 0;
|
|
|
ArrayUtils.fill(used, false);
|
|
|
- // We null all object mentries so that the garbage collector can do its work.
|
|
|
ArrayUtils.fill(keys, null);
|
|
|
}
|
|
|
|
|
|
@@ -324,13 +174,287 @@ public abstract class AHashLookup<K> extends Lookup<K> implements java.io.Serial
|
|
|
public final boolean isEmpty() {
|
|
|
return size == 0;
|
|
|
}
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public final Set<LookupEntry<K>> fastEntrySet() {
|
|
|
+ if (mentries == null) {
|
|
|
+ mentries = new MapEntrySet();
|
|
|
+ }
|
|
|
+ return mentries;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public final Set<K> keySet() {
|
|
|
+ if (mkeys == null) {
|
|
|
+ mkeys = new KeySet();
|
|
|
+ }
|
|
|
+ return mkeys;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public final IntCollection values() {
|
|
|
+ if (mvalues == null) {
|
|
|
+ mvalues = 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() {
|
|
|
+ AHashLookup.this.clear();
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return mvalues;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 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 final boolean trim() {
|
|
|
+ int l = HashCollection.arraysize(size, factor);
|
|
|
+ if (l >= n) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ irehash(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> mentries, 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.
|
|
|
+ * @see #trim()
|
|
|
+ */
|
|
|
+ public final boolean trim(int n) {
|
|
|
+ int l = MathUtils.pow2next((int) Math.ceil(n / factor));
|
|
|
+ if (this.n <= l) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ irehash(l);
|
|
|
+ } catch (OutOfMemoryError cantDoIt) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
- * The entry class for a hash map does not record keys and values, 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
|
|
|
+ * 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 values
|
|
|
+ * returned by this method is the same values as the one returned by the
|
|
|
+ * overriden method.
|
|
|
+ *
|
|
|
+ * @return a hash code for this map.
|
|
|
*/
|
|
|
+ @Override
|
|
|
+ public final int hashCode() {
|
|
|
+ int h = 0;
|
|
|
+ for (int j = size, i = 0, t = 0; j-- != 0;) {
|
|
|
+ while (!used[ i]) {
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+ if (this != keys[ i]) {
|
|
|
+ t = hash((K)keys[i]);
|
|
|
+ }
|
|
|
+ t ^= values[i];
|
|
|
+ h += t;
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+ return h;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public final boolean equals(Object object) {
|
|
|
+ return super.equals(object);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 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 final void irehash(int newN) {
|
|
|
+ int i = 0, pos;
|
|
|
+ boolean nused[] = this.used;
|
|
|
+ K ckey;
|
|
|
+ K ikeys[] = this.keys;
|
|
|
+ int ivalues[] = this.values;
|
|
|
+ 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(hash((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;
|
|
|
+ capacity = HashCollection.factorsize(n, factor);
|
|
|
+ this.keys = newKey;
|
|
|
+ this.values = newValue;
|
|
|
+ this.used = newUsed;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected int ifind(K key) {
|
|
|
+ // There's always an unused entry.
|
|
|
+ int i = HashUtils.murmurHash3(hash((K)key)) & mask;
|
|
|
+ while (used[i] && !compare(keys[i], key)) {
|
|
|
+ i = (i+1) & mask;
|
|
|
+ }
|
|
|
+ return i;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected int ireplace(int segment, int value) {
|
|
|
+ int prev = values[segment];
|
|
|
+ values[segment] = value;
|
|
|
+ return prev;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected int iinsert(int segment, K key, int value) {
|
|
|
+ used[segment] = true;
|
|
|
+ keys[segment] = key;
|
|
|
+ values[segment] = value;
|
|
|
+
|
|
|
+ if (++size >= capacity) {
|
|
|
+ irehash(HashCollection.arraysize(size + 1, factor));
|
|
|
+ }
|
|
|
+ return defRetValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected final int ishift(int pos) {
|
|
|
+ size--;
|
|
|
+ int prev = values[pos];
|
|
|
+ int last;
|
|
|
+ for (;;) {
|
|
|
+ last = pos;
|
|
|
+ pos = inext(pos);
|
|
|
+ if (!used[pos]) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ imove(last, pos);
|
|
|
+ }
|
|
|
+ iclear(last);
|
|
|
+ return prev;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected int inext(int last) {
|
|
|
+ int pos = (last+1) & mask;
|
|
|
+ while (used[pos]) {
|
|
|
+ int slot = HashUtils.murmurHash3(hash((K)keys[pos])) & mask;
|
|
|
+ if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ pos = (pos + 1) & mask;
|
|
|
+ }
|
|
|
+ return pos;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected void imove(int target, int source) {
|
|
|
+ keys[target] = keys[source];
|
|
|
+ values[target] = values[source];
|
|
|
+ }
|
|
|
+
|
|
|
+ protected void iclear(int segment) {
|
|
|
+ used[segment] = false;
|
|
|
+ keys[segment] = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void writeObject(java.io.ObjectOutputStream ostream) throws java.io.IOException {
|
|
|
+ K ikeys[] = this.keys;
|
|
|
+ int ivalues[] = this.values;
|
|
|
+ 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, factor);
|
|
|
+ capacity = HashCollection.factorsize(n, factor);
|
|
|
+ mask = n - 1;
|
|
|
+ K ikeys[] = this.keys = (K[]) new Object[n];
|
|
|
+ int ivalues[] = this.values = 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(hash((K)ckey)) & mask;
|
|
|
+ while (iused[ pos]) {
|
|
|
+ pos = (pos + 1) & mask;
|
|
|
+ }
|
|
|
+ iused[pos] = true;
|
|
|
+ ikeys[pos] = ckey;
|
|
|
+ ivalues[pos] = cval;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
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.
|
|
|
|
|
|
@@ -376,17 +500,17 @@ public abstract class AHashLookup<K> extends Lookup<K> implements java.io.Serial
|
|
|
Map.Entry<K, Integer> entry = (Map.Entry<K, Integer>) object;
|
|
|
K key = entry.getKey();
|
|
|
int v = entry.getValue();
|
|
|
- return cmpkey(keys[index], key) && (values[index] == v);
|
|
|
+ return compare(keys[index], key) && (values[index] == v);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public int hashCode() {
|
|
|
- return (hashkey((K)keys[ index])) ^ (values[ index]);
|
|
|
+ return (hash((K)keys[index])) ^ (values[index]);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public String toString() {
|
|
|
- return keys[ index] + "=>" + values[ index];
|
|
|
+ return keys[index] + "=>" + values[index];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -413,9 +537,9 @@ public abstract class AHashLookup<K> extends Lookup<K> implements java.io.Serial
|
|
|
int counter;
|
|
|
/**
|
|
|
* A lazily allocated list containing the mkeys 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).
|
|
|
+ * 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;
|
|
|
|
|
|
@@ -443,14 +567,9 @@ public abstract class AHashLookup<K> extends Lookup<K> implements java.io.Serial
|
|
|
// 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(hashkey(ckey)) & mask;
|
|
|
- // There's always an unused entry.
|
|
|
- while (used[ cpos]) {
|
|
|
- if (cmpkey(keys[ cpos], ckey)) {
|
|
|
- return cpos;
|
|
|
- }
|
|
|
- cpos = (cpos + 1) & mask;
|
|
|
+ int pos = ifind(ckey);
|
|
|
+ if(used[pos]) {
|
|
|
+ return pos;
|
|
|
}
|
|
|
}
|
|
|
last = pos;
|
|
|
@@ -463,43 +582,24 @@ public abstract class AHashLookup<K> extends Lookup<K> implements java.io.Serial
|
|
|
return last;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Shifts left mentries 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 keys.
|
|
|
- *
|
|
|
- * @param pos a starting position.
|
|
|
- * @return the position cleared by the shifting process.
|
|
|
- */
|
|
|
- protected final int shiftKeys(int pos) {
|
|
|
- // Shift mentries with the same hash.
|
|
|
- int ilast, islot;
|
|
|
+ protected final int ishift(int pos) {
|
|
|
+ int last;
|
|
|
for (;;) {
|
|
|
- pos = ((ilast = pos) + 1) & mask;
|
|
|
- while (used[ pos]) {
|
|
|
- islot = HashUtils.murmurHash3(hashkey((K)keys[ pos])) & mask;
|
|
|
- if (ilast <= pos ? ilast >= islot || islot > pos : ilast >= islot && islot > pos) {
|
|
|
- break;
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
+ last = pos;
|
|
|
+ pos = inext(last);
|
|
|
if (!used[ pos]) {
|
|
|
break;
|
|
|
}
|
|
|
- if (pos < ilast) {
|
|
|
- // Wrapped entry.
|
|
|
+ if (pos < last) {
|
|
|
if (wrapped == null) {
|
|
|
wrapped = new ArrayList<>();
|
|
|
}
|
|
|
- wrapped.add(keys[ pos]);
|
|
|
+ wrapped.add(keys[pos]);
|
|
|
}
|
|
|
- keys[ ilast] = keys[ pos];
|
|
|
- values[ ilast] = values[ pos];
|
|
|
+ imove(last, pos);
|
|
|
}
|
|
|
- used[ ilast] = false;
|
|
|
- keys[ ilast] = null;
|
|
|
- return ilast;
|
|
|
+ iclear(last);
|
|
|
+ return last;
|
|
|
}
|
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
@@ -514,7 +614,7 @@ public abstract class AHashLookup<K> extends Lookup<K> implements java.io.Serial
|
|
|
return;
|
|
|
}
|
|
|
size--;
|
|
|
- if (shiftKeys(last) == pos && counter > 0) {
|
|
|
+ if (ishift(last) == pos && counter > 0) {
|
|
|
counter++;
|
|
|
nextEntry();
|
|
|
}
|
|
|
@@ -581,18 +681,8 @@ public abstract class AHashLookup<K> extends Lookup<K> implements java.io.Serial
|
|
|
return false;
|
|
|
}
|
|
|
Map.Entry<K, Integer> entry = (Map.Entry<K, Integer>) object;
|
|
|
- K ckey = entry.getKey();
|
|
|
- // The starting point.
|
|
|
- int pos = HashUtils.murmurHash3(hashkey((K)ckey)) & mask;
|
|
|
- // There's always an unused entry.
|
|
|
- while (used[ pos]) {
|
|
|
- if (cmpkey(keys[ pos], ckey)) {
|
|
|
- int v = entry.getValue();
|
|
|
- return values[pos] == v;
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- return false;
|
|
|
+ int pos = ifind(entry.getKey());
|
|
|
+ return used[pos] && ValueUtils.equals(entry.getValue(), values[pos]);
|
|
|
}
|
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
@@ -602,18 +692,9 @@ public abstract class AHashLookup<K> extends Lookup<K> implements java.io.Serial
|
|
|
return false;
|
|
|
}
|
|
|
Map.Entry<K, Integer> entry = (Map.Entry<K, Integer>) object;
|
|
|
- K ckey = entry.getKey();
|
|
|
- // The starting point.
|
|
|
- int pos = HashUtils.murmurHash3(hashkey((K)ckey)) & mask;
|
|
|
- // There's always an unused entry.
|
|
|
- while (used[ pos]) {
|
|
|
- if (cmpkey(keys[pos], (K)ckey)) {
|
|
|
- AHashLookup.this.remove(entry.getKey());
|
|
|
- return true;
|
|
|
- }
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- return false;
|
|
|
+ int prev = size;
|
|
|
+ AHashLookup.this.remove(entry.getKey());
|
|
|
+ return size != prev;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@@ -627,22 +708,6 @@ public abstract class AHashLookup<K> extends Lookup<K> implements java.io.Serial
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- @Override
|
|
|
- public final Set<LookupEntry<K>> fastEntrySet() {
|
|
|
- if (mentries == null) {
|
|
|
- mentries = new MapEntrySet();
|
|
|
- }
|
|
|
- return mentries;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * An iterator on mkeys.
|
|
|
- *
|
|
|
- * <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 mkeys instead of mentries.
|
|
|
- */
|
|
|
private final class KeyIterator extends MapIterator implements Iterator<K> {
|
|
|
|
|
|
public KeyIterator() {
|
|
|
@@ -687,22 +752,6 @@ public abstract class AHashLookup<K> extends Lookup<K> implements java.io.Serial
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- @Override
|
|
|
- public final Set<K> keySet() {
|
|
|
- if (mkeys == null) {
|
|
|
- mkeys = new KeySet();
|
|
|
- }
|
|
|
- return mkeys;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * An iterator on mvalues.
|
|
|
- *
|
|
|
- * <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 mvalues instead of mentries.
|
|
|
- */
|
|
|
private final class ValueIterator extends IntIterator {
|
|
|
|
|
|
private final MapIterator delegate = new MapIterator();
|
|
|
@@ -725,203 +774,4 @@ public abstract class AHashLookup<K> extends Lookup<K> implements java.io.Serial
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- @Override
|
|
|
- public final IntCollection values() {
|
|
|
- if (mvalues == null) {
|
|
|
- mvalues = 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() {
|
|
|
- AHashLookup.this.clear();
|
|
|
- }
|
|
|
- };
|
|
|
- }
|
|
|
- return mvalues;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 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 final boolean trim() {
|
|
|
- int l = HashCollection.arraysize(size, factor);
|
|
|
- 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> mentries, 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 final boolean trim(int n) {
|
|
|
- int l = MathUtils.pow2next((int) Math.ceil(n / factor));
|
|
|
- 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 final void rehash(int newN) {
|
|
|
- int i = 0, pos;
|
|
|
- boolean nused[] = this.used;
|
|
|
- K ckey;
|
|
|
- K ikeys[] = this.keys;
|
|
|
- int ivalues[] = this.values;
|
|
|
- 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(hashkey((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.factorsize(n, factor);
|
|
|
- this.keys = newKey;
|
|
|
- this.values = 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 values
|
|
|
- returned by this method is the same values as the one returned by the
|
|
|
- overriden method.
|
|
|
- *
|
|
|
- * @return a hash code for this map.
|
|
|
- */
|
|
|
- @Override
|
|
|
- public final int hashCode() {
|
|
|
- int h = 0;
|
|
|
- for (int j = size, i = 0, t = 0; j-- != 0;) {
|
|
|
- while (!used[ i]) {
|
|
|
- i++;
|
|
|
- }
|
|
|
- if (this != keys[ i]) {
|
|
|
- t = hashkey((K)keys[i]);
|
|
|
- }
|
|
|
- t ^= values[i];
|
|
|
- h += t;
|
|
|
- i++;
|
|
|
- }
|
|
|
- return h;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public final boolean equals(Object object) {
|
|
|
- return super.equals(object);
|
|
|
- }
|
|
|
-
|
|
|
- private void writeObject(java.io.ObjectOutputStream ostream) throws java.io.IOException {
|
|
|
- K ikeys[] = this.keys;
|
|
|
- int ivalues[] = this.values;
|
|
|
- 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, factor);
|
|
|
- maxFill = HashCollection.factorsize(n, factor);
|
|
|
- mask = n - 1;
|
|
|
- K ikeys[] = this.keys = (K[]) new Object[n];
|
|
|
- int ivalues[] = this.values = 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(hashkey((K)ckey)) & mask;
|
|
|
- while (iused[ pos]) {
|
|
|
- pos = (pos + 1) & mask;
|
|
|
- }
|
|
|
- iused[pos] = true;
|
|
|
- ikeys[pos] = ckey;
|
|
|
- ivalues[pos] = cval;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
}
|