Compact HashMap
//package net.ontopia.utils;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
public class CompactHashMap<K, V> extends AbstractMap<K, V> {
protected final static int INITIAL_SIZE = 3;
protected final static double LOAD_FACTOR = 0.6;
/**
* This object is used to represent null, should clients use that as a key.
*/
protected final static Object nullObject = new Object();
/**
* When a key is deleted this object is put into the hashtable in its place,
* so that other entries with the same key (collisions) further down the
* hashtable are not lost after we delete an object in the collision chain.
*/
protected final static Object deletedObject = new Object();
protected int elements;
protected int freecells;
protected K[] keys;
protected V[] values; // object at pos x corresponds to key at pos x
protected int modCount;
/**
* Constructs a new, empty set.
*/
public CompactHashMap() {
this(INITIAL_SIZE);
}
/**
* Constructs a new, empty set.
*/
public CompactHashMap(int size) {
// NOTE: If array size is 0, we get a
// "java.lang.ArithmeticException: / by zero" in add(Object).
keys = (K[]) new Object[(size == 0 ? 1 : size)];
values = (V[]) new Object[(size == 0 ? 1 : size)];
elements = 0;
freecells = keys.length;
modCount = 0;
}
// MAP IMPLEMENTATION
/**
* Returns the number of key/value mappings in this map.
*/
public int size() {
return elements;
}
/**
* Returns <tt>true</tt> if this map contains no mappings.
*/
public boolean isEmpty() {
return elements == 0;
}
/**
* Removes all key/value mappings in the map.
*/
public void clear() {
elements = 0;
for (int ix = 0; ix < keys.length; ix++) {
keys[ix] = null;
values[ix] = null;
}
freecells = values.length;
modCount++;
}
/**
* Returns <tt>true</tt> if this map contains the specified key.
*/
public boolean containsKey(Object k) {
return keys[findKeyIndex(k)] != null;
}
/**
* Returns <tt>true</tt> if this map contains the specified value.
*/
public boolean containsValue(Object v) {
if (v == null)
v = (V) nullObject;
for (int ix = 0; ix < values.length; ix++)
if (values[ix] != null && values[ix].equals(v))
return true;
return false;
}
/**
* Returns a read-only set view of the map's keys.
*/
public Set<Entry<K, V>> entrySet() {
throw new UnsupportedOperationException();
}
/**
* Removes the mapping with key k, if there is one, and returns its value,
* if there is one, and null if there is none.
*/
public V remove(Object k) {
int index = findKeyIndex(k);
// we found the right position, now do the removal
if (keys[index] != null) {
// we found the object
// same problem here as with put
V v = values[index];
keys[index] = (K) deletedObject;
values[index] = (V) deletedObject;
modCount++;
elements--;
return v;
} else
// we did not find the key
return null;
}
/**
* Adds the specified mapping to this map, returning the old value for the
* mapping, if there was one.
*/
public V put(K k, V v) {
if (k == null)
k = (K) nullObject;
int hash = k.hashCode();
int index = (hash & 0x7FFFFFFF) % keys.length;
int offset = 1;
int deletedix = -1;
// search for the key (continue while !null and !this key)
while (keys[index] != null
&& !(keys[index].hashCode() == hash && keys[index].equals(k))) {
// if there's a deleted mapping here we can put this mapping here,
// provided it's not in here somewhere else already
if (keys[index] == deletedObject)
deletedix = index;
index = ((index + offset) & 0x7FFFFFFF) % keys.length;
offset = offset * 2 + 1;
if (offset == -1)
offset = 2;
}
if (keys[index] == null) { // wasn't present already
if (deletedix != -1) // reusing a deleted cell
index = deletedix;
else
freecells--;
modCount++;
elements++;
keys[index] = (K) k;
values[index] = (V) v;
// rehash with increased capacity
if (1 - (freecells / (double) keys.length) > LOAD_FACTOR)
rehash(keys.length * 2 + 1);
return null;
} else { // was there already
modCount++;
V oldv = values[index];
values[index] = (V) v;
return oldv;
}
}
/**
* INTERNAL: Rehashes the hashmap to a bigger size.
*/
protected void rehash(int newCapacity) {
int oldCapacity = keys.length;
K[] newKeys = (K[]) new Object[newCapacity];
V[] newValues = (V[]) new Object[newCapacity];
for (int ix = 0; ix < oldCapacity; ix++) {
Object k = keys[ix];
if (k == null || k == deletedObject)
continue;
int hash = k.hashCode();
int index = (hash & 0x7FFFFFFF) % newCapacity;
int offset = 1;
// search for the key
while (newKeys[index] != null) { // no need to test for duplicates
index = ((index + offset) & 0x7FFFFFFF) % newCapacity;
offset = offset * 2 + 1;
if (offset == -1)
offset = 2;
}
newKeys[index] = (K) k;
newValues[index] = values[ix];
}
keys = newKeys;
values = newValues;
freecells = keys.length - elements;
}
/**
* Returns the value for the key k, if there is one, and null if there is
* none.
*/
public V get(Object k) {
return values[findKeyIndex(k)];
}
/**
* Returns a virtual read-only collection containing all the values in the
* map.
*/
public Collection<V> values() {
return new ValueCollection();
}
/**
* Returns a virtual read-only set of all the keys in the map.
*/
public Set<K> keySet() {
return new KeySet();
}
// --- Internal utilities
private final int findKeyIndex(Object k) {
if (k == null)
k = nullObject;
int hash = k.hashCode();
int index = (hash & 0x7FFFFFFF) % keys.length;
int offset = 1;
// search for the key (continue while !null and !this key)
while (keys[index] != null
&& !(keys[index].hashCode() == hash && keys[index].equals(k))) {
index = ((index + offset) & 0x7FFFFFFF) % keys.length;
offset = offset * 2 + 1;
if (offset == -1)
offset = 2;
}
return index;
}
// --- Key set
private class KeySet<K> extends AbstractSet<K> {
public int size() {
return elements;
}
public boolean contains(Object k) {
return containsKey(k);
}
public Iterator<K> iterator() {
return new KeyIterator();
}
}
private class KeyIterator<K> implements Iterator<K> {
private int ix;
private KeyIterator() {
// walk up to first value, so that hasNext() and next() return
// correct results
for (; ix < keys.length; ix++)
if (values[ix] != null && keys[ix] != deletedObject)
break;
}
public boolean hasNext() {
return ix < keys.length;
}
public void remove() {
throw new UnsupportedOperationException("Collection is read-only");
}
public K next() {
if (ix >= keys.length)
throw new NoSuchElementException();
K key = (K) keys[ix++];
// walk up to next value
for (; ix < keys.length; ix++)
if (keys[ix] != null && keys[ix] != deletedObject)
break;
// ix now either points to next key, or outside array (if no next)
return key;
}
}
// --- Value collection
private class ValueCollection<V> extends AbstractCollection<V> {
public int size() {
return elements;
}
public Iterator<V> iterator() {
return new ValueIterator();
}
public boolean contains(Object v) {
return containsValue(v);
}
}
private class ValueIterator<V> implements Iterator<V> {
private int ix;
private ValueIterator() {
// walk up to first value, so that hasNext() and next() return
// correct results
for (; ix < values.length; ix++)
if (values[ix] != null && values[ix] != deletedObject)
break;
}
public boolean hasNext() {
return ix < values.length;
}
public void remove() {
throw new UnsupportedOperationException("Collection is read-only");
}
public V next() {
if (ix >= values.length)
throw new NoSuchElementException();
V value = (V) values[ix++];
// walk up to next value
for (; ix < values.length; ix++)
if (values[ix] != null && values[ix] != deletedObject)
break;
// ix now either points to next value, or outside array (if no next)
return value;
}
}
}
Related examples in the same category