Mega Code Archive

 
Categories / Java / Collections Data Structure
 

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;     }   } }