summaryrefslogtreecommitdiff
path: root/java/util/IdentityHashMap.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/util/IdentityHashMap.java')
-rw-r--r--java/util/IdentityHashMap.java230
1 files changed, 144 insertions, 86 deletions
diff --git a/java/util/IdentityHashMap.java b/java/util/IdentityHashMap.java
index e64f7d532..8dead96c1 100644
--- a/java/util/IdentityHashMap.java
+++ b/java/util/IdentityHashMap.java
@@ -97,16 +97,13 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
private static final int DEFAULT_CAPACITY = 21;
/**
- * This object is used to mark deleted items. Package visible for use by
- * nested classes.
+ * This object is used to mark a slot whose key or value is 'null'.
+ * This is more efficient than using a special value to mark an empty
+ * slot, because null entries are rare, empty slots are common, and
+ * the JVM will clear new arrays for us.
+ * Package visible for use by nested classes.
*/
- static final Object tombstone = new Object();
-
- /**
- * This object is used to mark empty slots. We need this because
- * using null is ambiguous. Package visible for use by nested classes.
- */
- static final Object emptyslot = new Object();
+ static final Object nullslot = new Object();
/**
* Compatible with JDK 1.4.
@@ -166,7 +163,6 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
if (max < 2)
max = 2;
table = new Object[max << 1];
- Arrays.fill(table, emptyslot);
threshold = (max >> 2) * 3;
}
@@ -191,7 +187,7 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
if (size != 0)
{
modCount++;
- Arrays.fill(table, emptyslot);
+ Arrays.fill(table, null);
size = 0;
}
}
@@ -227,6 +223,7 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
*/
public boolean containsKey(Object key)
{
+ key = xform(key);
return key == table[hash(key)];
}
@@ -241,6 +238,7 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
*/
public boolean containsValue(Object value)
{
+ value = xform(value);
for (int i = table.length - 1; i > 0; i -= 2)
if (table[i] == value)
return true;
@@ -299,7 +297,9 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
if (! (o instanceof Map.Entry))
return false;
Map.Entry m = (Map.Entry) o;
- return m.getValue() == table[hash(m.getKey()) + 1];
+ Object value = xform(m.getValue());
+ Object key = xform(m.getKey());
+ return value == table[hash(key) + 1];
}
public int hashCode()
@@ -311,14 +311,13 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
{
if (! (o instanceof Map.Entry))
return false;
- Object key = ((Map.Entry) o).getKey();
+ Object key = xform(((Map.Entry) o).getKey());
int h = hash(key);
if (table[h] == key)
{
size--;
modCount++;
- table[h] = tombstone;
- table[h + 1] = tombstone;
+ IdentityHashMap.this.removeAtIndex(h);
return true;
}
return false;
@@ -360,8 +359,9 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
*/
public V get(Object key)
{
+ key = xform(key);
int h = hash(key);
- return (V) (table[h] == key ? table[h + 1] : null);
+ return (V) (table[h] == key ? unxform(table[h + 1]) : null);
}
/**
@@ -378,10 +378,11 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
for (int i = table.length - 2; i >= 0; i -= 2)
{
Object key = table[i];
- if (key == emptyslot || key == tombstone)
+ if (key == null)
continue;
- hash += (System.identityHashCode(key)
- ^ System.identityHashCode(table[i + 1]));
+ // FIXME: this is a lame computation.
+ hash += (System.identityHashCode(unxform(key))
+ ^ System.identityHashCode(unxform(table[i + 1])));
}
return hash;
}
@@ -445,23 +446,22 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
for (int i = table.length - 2; i >= 0; i -= 2)
{
Object key = table[i];
- if (key == emptyslot || key == tombstone)
+ if (key == null)
continue;
- hash += System.identityHashCode(key);
+ hash += System.identityHashCode(unxform(key));
}
return hash;
-
}
public boolean remove(Object o)
{
+ o = xform(o);
int h = hash(o);
if (table[h] == o)
{
size--;
modCount++;
- table[h] = tombstone;
- table[h + 1] = tombstone;
+ removeAtIndex(h);
return true;
}
return false;
@@ -486,6 +486,18 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
*/
public V put(K key, V value)
{
+ key = (K) xform(key);
+ value = (V) xform(value);
+
+ // We don't want to rehash if we're overwriting an existing slot.
+ int h = hash(key);
+ if (table[h] == key)
+ {
+ V r = (V) unxform(table[h + 1]);
+ table[h + 1] = value;
+ return r;
+ }
+
// Rehash if the load factor is too high.
if (size > threshold)
{
@@ -493,25 +505,25 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
// This isn't necessarily prime, but it is an odd number of key/value
// slots, which has a higher probability of fewer collisions.
table = new Object[(old.length * 2) + 2];
- Arrays.fill(table, emptyslot);
size = 0;
threshold = (table.length >>> 3) * 3;
for (int i = old.length - 2; i >= 0; i -= 2)
{
K oldkey = (K) old[i];
- if (oldkey != tombstone && oldkey != emptyslot)
- // Just use put. This isn't very efficient, but it is ok.
- put(oldkey, (V) old[i + 1]);
+ if (oldkey != null)
+ {
+ h = hash(oldkey);
+ table[h] = oldkey;
+ table[h + 1] = old[i + 1];
+ ++size;
+ // No need to update modCount here, we'll do it
+ // just after the loop.
+ }
}
- }
- int h = hash(key);
- if (table[h] == key)
- {
- Object r = table[h + 1];
- table[h + 1] = value;
- return (V) r;
+ // Now that we've resize, recompute the hash value.
+ h = hash(key);
}
// At this point, we add a new mapping.
@@ -536,6 +548,40 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
}
/**
+ * Remove the element at index and update the table to compensate.
+ * This is package-private for use by inner classes.
+ * @param i index of the removed element
+ */
+ final void removeAtIndex(int i)
+ {
+ // This is Algorithm R from Knuth, section 6.4.
+ // Variable names are taken directly from the text.
+ while (true)
+ {
+ table[i] = null;
+ table[i + 1] = null;
+ int j = i;
+ int r;
+ do
+ {
+ i -= 2;
+ if (i < 0)
+ i = table.length - 2;
+ Object key = table[i];
+ if (key == null)
+ return;
+ r = Math.abs(System.identityHashCode(key)
+ % (table.length >> 1)) << 1;
+ }
+ while ((i <= r && r < j)
+ || (r < j && j < i)
+ || (j < i && i <= r));
+ table[j] = table[i];
+ table[j + 1] = table[i + 1];
+ }
+ }
+
+ /**
* Removes from the HashMap and returns the value which is mapped by
* the supplied key. If the key maps to nothing, then the HashMap
* remains unchanged, and <code>null</code> is returned.
@@ -551,14 +597,14 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
*/
public V remove(Object key)
{
+ key = xform(key);
int h = hash(key);
if (table[h] == key)
{
modCount++;
size--;
- Object r = table[h + 1];
- table[h] = tombstone;
- table[h + 1] = tombstone;
+ Object r = unxform(table[h + 1]);
+ removeAtIndex(h);
return (V) r;
}
return null;
@@ -613,13 +659,14 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
public boolean remove(Object o)
{
+ o = xform(o);
+ // This approach may look strange, but it is ok.
for (int i = table.length - 1; i > 0; i -= 2)
if (table[i] == o)
{
modCount++;
- table[i - 1] = tombstone;
- table[i] = tombstone;
size--;
+ IdentityHashMap.this.removeAtIndex(i - 1);
return true;
}
return false;
@@ -629,8 +676,31 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
}
/**
+ * Transform a reference from its external form to its internal form.
+ * This is package-private for use by inner classes.
+ */
+ final Object xform(Object o)
+ {
+ if (o == null)
+ o = nullslot;
+ return o;
+ }
+
+ /**
+ * Transform a reference from its internal form to its external form.
+ * This is package-private for use by inner classes.
+ */
+ final Object unxform(Object o)
+ {
+ if (o == nullslot)
+ o = null;
+ return o;
+ }
+
+ /**
* Helper method which computes the hash code, then traverses the table
- * until it finds the key, or the spot where the key would go.
+ * until it finds the key, or the spot where the key would go. the key
+ * must already be in its internal form.
*
* @param key the key to check
* @return the index where the key belongs
@@ -638,36 +708,23 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
* @see #put(Object, Object)
*/
// Package visible for use by nested classes.
- int hash(Object key)
+ final int hash(Object key)
{
- // Implementation note: it is feasible for the table to have no
- // emptyslots, if it is full with entries and tombstones, so we must
- // remember where we started. If we encounter the key or an emptyslot,
- // we are done. If we encounter a tombstone, the key may still be in
- // the array. If we don't encounter the key, we use the first emptyslot
- // or tombstone we encountered as the location where the key would go.
- // By requiring at least 2 key/value slots, and rehashing at 75%
- // capacity, we guarantee that there will always be either an emptyslot
- // or a tombstone somewhere in the table.
int h = Math.abs(System.identityHashCode(key) % (table.length >> 1)) << 1;
- int del = -1;
- int save = h;
- do
+ while (true)
{
- if (table[h] == key)
+ // By requiring at least 2 key/value slots, and rehashing at 75%
+ // capacity, we guarantee that there will always be either an empty
+ // slot somewhere in the table.
+ if (table[h] == key || table[h] == null)
return h;
- if (table[h] == emptyslot)
- break;
- if (table[h] == tombstone && del < 0)
- del = h;
+ // We use linear probing as it is friendlier to the cache and
+ // it lets us efficiently remove entries.
h -= 2;
if (h < 0)
h = table.length - 2;
}
- while (h != save);
-
- return del < 0 ? h : del;
}
/**
@@ -731,10 +788,11 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
loc -= 2;
key = table[loc];
}
- while (key == emptyslot || key == tombstone);
-
- return (I) (type == KEYS ? key : (type == VALUES ? table[loc + 1]
- : new IdentityEntry(loc)));
+ while (key == null);
+
+ return (I) (type == KEYS ? unxform(key)
+ : (type == VALUES ? unxform(table[loc + 1])
+ : new IdentityEntry(loc)));
}
/**
@@ -748,12 +806,11 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
{
if (knownMod != modCount)
throw new ConcurrentModificationException();
- if (loc == table.length || table[loc] == tombstone)
+ if (loc == table.length)
throw new IllegalStateException();
modCount++;
size--;
- table[loc] = tombstone;
- table[loc + 1] = tombstone;
+ removeAtIndex(loc);
knownMod++;
}
} // class IdentityIterator
@@ -797,12 +854,13 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
*/
public boolean equals(Object o)
{
- if (knownMod != modCount || table[loc] == tombstone)
+ if (knownMod != modCount)
throw new ConcurrentModificationException();
if (! (o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry) o;
- return table[loc] == e.getKey() && table[loc + 1] == e.getValue();
+ return table[loc] == xform(e.getKey())
+ && table[loc + 1] == xform(e.getValue());
}
/**
@@ -814,9 +872,9 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
*/
public EK getKey()
{
- if (knownMod != modCount || table[loc] == tombstone)
+ if (knownMod != modCount)
throw new ConcurrentModificationException();
- return (EK) table[loc];
+ return (EK) unxform(table[loc]);
}
/**
@@ -828,9 +886,9 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
*/
public EV getValue()
{
- if (knownMod != modCount || table[loc] == tombstone)
+ if (knownMod != modCount)
throw new ConcurrentModificationException();
- return (EV) table[loc + 1];
+ return (EV) unxform(table[loc + 1]);
}
/**
@@ -844,10 +902,10 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
*/
public int hashCode()
{
- if (knownMod != modCount || table[loc] == tombstone)
+ if (knownMod != modCount)
throw new ConcurrentModificationException();
- return (System.identityHashCode(table[loc])
- ^ System.identityHashCode(table[loc + 1]));
+ return (System.identityHashCode(unxform(table[loc]))
+ ^ System.identityHashCode(unxform(table[loc + 1])));
}
/**
@@ -860,10 +918,10 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
*/
public EV setValue(EV value)
{
- if (knownMod != modCount || table[loc] == tombstone)
+ if (knownMod != modCount)
throw new ConcurrentModificationException();
- EV r = (EV) table[loc + 1];
- table[loc + 1] = value;
+ EV r = (EV) unxform(table[loc + 1]);
+ table[loc + 1] = xform(value);
return r;
}
@@ -877,9 +935,9 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
*/
public String toString()
{
- if (knownMod != modCount || table[loc] == tombstone)
+ if (knownMod != modCount)
throw new ConcurrentModificationException();
- return table[loc] + "=" + table[loc + 1];
+ return unxform(table[loc]) + "=" + unxform(table[loc + 1]);
}
} // class IdentityEntry
@@ -922,10 +980,10 @@ public class IdentityHashMap<K,V> extends AbstractMap<K,V>
for (int i = table.length - 2; i >= 0; i -= 2)
{
Object key = table[i];
- if (key != tombstone && key != emptyslot)
+ if (key != null)
{
- s.writeObject(key);
- s.writeObject(table[i + 1]);
+ s.writeObject(unxform(key));
+ s.writeObject(unxform(table[i + 1]));
}
}
}