Java tutorial
//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; } } }