Mega Code Archive

 
Categories / Java / Collections Data Structure
 

Object Int Map

/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.  *   * This program and the accompanying materials are made available under  * the terms of the Common Public License v1.0 which accompanies this distribution,  * and is available at http://www.eclipse.org/legal/cpl-v10.html  *   * $Id: ObjectIntMap.java,v 1.1.1.1 2004/05/09 16:57:54 vlad_r Exp $  */ // ---------------------------------------------------------------------------- /**  *  * MT-safety: an instance of this class is <I>not</I> safe for access from  * multiple concurrent threads [even if access is done by a single thread at a  * time]. The caller is expected to synchronize externally on an instance [the  * implementation does not do internal synchronization for the sake of efficiency].  * java.util.ConcurrentModificationException is not supported either.  *  * @author (C) 2001, Vlad Roubtsov  */ public final class ObjectIntMap {     // public: ................................................................          // TODO: optimize key comparisons using key.hash == entry.key.hash condition     /**      * Equivalent to <CODE>IntObjectMap(11, 0.75F)</CODE>.      */     public ObjectIntMap ()     {         this (11, 0.75F);     }          /**      * Equivalent to <CODE>IntObjectMap(capacity, 0.75F)</CODE>.      */     public ObjectIntMap (final int initialCapacity)     {         this (initialCapacity, 0.75F);     }          /**      * Constructs an IntObjectMap with specified initial capacity and load factor.      *      * @param initialCapacity initial number of hash buckets in the table [may not be negative, 0 is equivalent to 1].      * @param loadFactor the load factor to use to determine rehashing points [must be in (0.0, 1.0] range].      */     public ObjectIntMap (int initialCapacity, final float loadFactor)     {         if (initialCapacity < 0) throw new IllegalArgumentException ("negative input: initialCapacity [" + initialCapacity + "]");         if ((loadFactor <= 0.0) || (loadFactor >= 1.0 + 1.0E-6))             throw new IllegalArgumentException ("loadFactor not in (0.0, 1.0] range: " + loadFactor);                  if (initialCapacity == 0) initialCapacity = 1;                  m_loadFactor = loadFactor > 1.0 ? 1.0F : loadFactor;                 m_sizeThreshold = (int) (initialCapacity * loadFactor);         m_buckets = new Entry [initialCapacity];     }               /**      * Overrides Object.toString() for debug purposes.      */     public String toString ()     {         final StringBuffer s = new StringBuffer ();         debugDump (s);                  return s.toString ();     }          /**      * Returns the number of key-value mappings in this map.      */     public int size ()     {         return m_size;     }     public boolean contains (final Object key)     {                  // index into the corresponding hash bucket:         final Entry [] buckets = m_buckets;         final int keyHash = key.hashCode ();         final int bucketIndex = (keyHash & 0x7FFFFFFF) % buckets.length;                  // traverse the singly-linked list of entries in the bucket:         for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)         {             if ((keyHash == entry.m_key.hashCode ()) || entry.m_key.equals (key))                 return true;         }                  return false;     }          /**      * Returns the value that is mapped to a given 'key'. Returns      * false if this key has never been mapped.      *      * @param key mapping key [may not be null]      * @param out holder for the found value [must be at least of size 1]      *      * @return 'true' if this key was mapped to an existing value      */     public boolean get (final Object key, final int [] out)     {                  // index into the corresponding hash bucket:         final Entry [] buckets = m_buckets;         final int keyHash = key.hashCode ();         final int bucketIndex = (keyHash & 0x7FFFFFFF) % buckets.length;                  // traverse the singly-linked list of entries in the bucket:         for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)         {             if ((keyHash == entry.m_key.hashCode ()) || entry.m_key.equals (key))             {                 out [0] = entry.m_value;                 return true;             }         }                  return false;     }          public Object [] keys ()     {         final Object [] result = new Object [m_size];         int scan = 0;                  for (int b = 0; b < m_buckets.length; ++ b)         {             for (Entry entry = m_buckets [b]; entry != null; entry = entry.m_next)             {                 result [scan ++] = entry.m_key;             }         }                  return result;     }          /**      * Updates the table to map 'key' to 'value'. Any existing mapping is overwritten.      *      * @param key mapping key [may not be null]      * @param value mapping value.      */     public void put (final Object key, final int value)     {                  Entry currentKeyEntry = null;                  // detect if 'key' is already in the table [in which case, set 'currentKeyEntry' to point to its entry]:                  // index into the corresponding hash bucket:         final int keyHash = key.hashCode ();         int bucketIndex = (keyHash & 0x7FFFFFFF) % m_buckets.length;                  // traverse the singly-linked list of entries in the bucket:         Entry [] buckets = m_buckets;         for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)         {             if ((keyHash == entry.m_key.hashCode ()) || entry.m_key.equals (key))             {                 currentKeyEntry = entry;                 break;             }         }                  if (currentKeyEntry != null)         {             // replace the current value:                              currentKeyEntry.m_value = value;         }         else         {             // add a new entry:                          if (m_size >= m_sizeThreshold) rehash ();                          buckets = m_buckets;             bucketIndex = (keyHash & 0x7FFFFFFF) % buckets.length;             final Entry bucketListHead = buckets [bucketIndex];             final Entry newEntry = new Entry (key, value, bucketListHead);             buckets [bucketIndex] = newEntry;                          ++ m_size;         }     }          /**      * Updates the table to map 'key' to 'value'. Any existing mapping is overwritten.      *      * @param key mapping key [may not be null]      */     public void remove (final Object key)     {                  // index into the corresponding hash bucket:         final int keyHash = key.hashCode ();         final int bucketIndex = (keyHash  & 0x7FFFFFFF) % m_buckets.length;                  // traverse the singly-linked list of entries in the bucket:         Entry [] buckets = m_buckets;         for (Entry entry = buckets [bucketIndex], prev = entry; entry != null; )         {             final Entry next = entry.m_next;                          if ((keyHash == entry.m_key.hashCode ()) || entry.m_key.equals (key))             {                 if (prev == entry)                     buckets [bucketIndex] = next;                 else                     prev.m_next = next;                                  -- m_size;                      break;             }                          prev = entry;             entry = next;         }     }          // protected: .............................................................     // package: ...............................................................               void debugDump (final StringBuffer out)     {         if (out != null)         {             out.append (super.toString ()); out.append (EOL);             out.append ("size = " + m_size + ", bucket table size = " + m_buckets.length + ", load factor = " + m_loadFactor + EOL);             out.append ("size threshold = " + m_sizeThreshold + EOL);         }     }     // private: ...............................................................          /**      * The structure used for chaining colliding keys.      */     private static final class Entry     {         Entry (final Object key, final int value, final Entry next)         {             m_key = key;              m_value = value;             m_next = next;         }                  Object m_key;     // reference to the value [never null]         int m_value;                  Entry m_next; // singly-linked list link              } // end of nested class          /**      * Re-hashes the table into a new array of buckets.      */     private void rehash ()     {         // TODO: it is possible to run this method twice, first time using the 2*k+1 prime sequencer for newBucketCount         // and then with that value reduced to actually shrink capacity. As it is right now, the bucket table can         // only grow in size                  final Entry [] buckets = m_buckets;                  final int newBucketCount = (m_buckets.length << 1) + 1;         final Entry [] newBuckets = new Entry [newBucketCount];         // rehash all entry chains in every bucket:         for (int b = 0; b < buckets.length; ++ b)         {             for (Entry entry = buckets [b]; entry != null; )             {                 final Entry next = entry.m_next; // remember next pointer because we are going to reuse this entry                 final int entryKeyHash = entry.m_key.hashCode () & 0x7FFFFFFF;                              // index into the corresponding new hash bucket:                 final int newBucketIndex = entryKeyHash % newBucketCount;                                  final Entry bucketListHead = newBuckets [newBucketIndex];                 entry.m_next = bucketListHead;                 newBuckets [newBucketIndex] = entry;                                                                  entry = next;             }         }                  m_sizeThreshold = (int) (newBucketCount * m_loadFactor);         m_buckets = newBuckets;     }               private final float m_loadFactor; // determines the setting of m_sizeThreshold          private Entry [] m_buckets; // table of buckets     private int m_size; // number of keys in the table, not cleared as of last check     private int m_sizeThreshold; // size threshold for rehashing              private static final String EOL = System.getProperty ("line.separator", "\n");      } // end of class // ----------------------------------------------------------------------------