• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
2  *
3  * This program and the accompanying materials are made available under
4  * the terms of the Common Public License v1.0 which accompanies this distribution,
5  * and is available at http://www.eclipse.org/legal/cpl-v10.html
6  *
7  * $Id: ObjectIntMap.java,v 1.1.1.1 2004/05/09 16:57:54 vlad_r Exp $
8  */
9 package com.vladium.util;
10 
11 import com.vladium.util.asserts.$assert;
12 
13 // ----------------------------------------------------------------------------
14 /**
15  *
16  * MT-safety: an instance of this class is <I>not</I> safe for access from
17  * multiple concurrent threads [even if access is done by a single thread at a
18  * time]. The caller is expected to synchronize externally on an instance [the
19  * implementation does not do internal synchronization for the sake of efficiency].
20  * java.util.ConcurrentModificationException is not supported either.
21  *
22  * @author (C) 2001, Vlad Roubtsov
23  */
24 public
25 final class ObjectIntMap
26 {
27     // public: ................................................................
28 
29     // TODO: optimize key comparisons using key.hash == entry.key.hash condition
30 
31     /**
32      * Equivalent to <CODE>IntObjectMap(11, 0.75F)</CODE>.
33      */
ObjectIntMap()34     public ObjectIntMap ()
35     {
36         this (11, 0.75F);
37     }
38 
39     /**
40      * Equivalent to <CODE>IntObjectMap(capacity, 0.75F)</CODE>.
41      */
ObjectIntMap(final int initialCapacity)42     public ObjectIntMap (final int initialCapacity)
43     {
44         this (initialCapacity, 0.75F);
45     }
46 
47     /**
48      * Constructs an IntObjectMap with specified initial capacity and load factor.
49      *
50      * @param initialCapacity initial number of hash buckets in the table [may not be negative, 0 is equivalent to 1].
51      * @param loadFactor the load factor to use to determine rehashing points [must be in (0.0, 1.0] range].
52      */
ObjectIntMap(int initialCapacity, final float loadFactor)53     public ObjectIntMap (int initialCapacity, final float loadFactor)
54     {
55         if (initialCapacity < 0) throw new IllegalArgumentException ("negative input: initialCapacity [" + initialCapacity + "]");
56         if ((loadFactor <= 0.0) || (loadFactor >= 1.0 + 1.0E-6))
57             throw new IllegalArgumentException ("loadFactor not in (0.0, 1.0] range: " + loadFactor);
58 
59         if (initialCapacity == 0) initialCapacity = 1;
60 
61         m_loadFactor = loadFactor > 1.0 ? 1.0F : loadFactor;
62         m_sizeThreshold = (int) (initialCapacity * loadFactor);
63         m_buckets = new Entry [initialCapacity];
64     }
65 
66 
67     /**
68      * Overrides Object.toString() for debug purposes.
69      */
toString()70     public String toString ()
71     {
72         final StringBuffer s = new StringBuffer ();
73         debugDump (s);
74 
75         return s.toString ();
76     }
77 
78     /**
79      * Returns the number of key-value mappings in this map.
80      */
size()81     public int size ()
82     {
83         return m_size;
84     }
85 
contains(final Object key)86     public boolean contains (final Object key)
87     {
88         if ($assert.ENABLED) $assert.ASSERT (key != null, "null input: key");
89 
90         // index into the corresponding hash bucket:
91         final Entry [] buckets = m_buckets;
92         final int keyHash = key.hashCode ();
93         final int bucketIndex = (keyHash & 0x7FFFFFFF) % buckets.length;
94 
95         // traverse the singly-linked list of entries in the bucket:
96         for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
97         {
98             if ((keyHash == entry.m_key.hashCode ()) || entry.m_key.equals (key))
99                 return true;
100         }
101 
102         return false;
103     }
104 
105     /**
106      * Returns the value that is mapped to a given 'key'. Returns
107      * false if this key has never been mapped.
108      *
109      * @param key mapping key [may not be null]
110      * @param out holder for the found value [must be at least of size 1]
111      *
112      * @return 'true' if this key was mapped to an existing value
113      */
get(final Object key, final int [] out)114     public boolean get (final Object key, final int [] out)
115     {
116         if ($assert.ENABLED) $assert.ASSERT (key != null, "null input: key");
117 
118         // index into the corresponding hash bucket:
119         final Entry [] buckets = m_buckets;
120         final int keyHash = key.hashCode ();
121         final int bucketIndex = (keyHash & 0x7FFFFFFF) % buckets.length;
122 
123         // traverse the singly-linked list of entries in the bucket:
124         for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
125         {
126             if ((keyHash == entry.m_key.hashCode ()) || entry.m_key.equals (key))
127             {
128                 out [0] = entry.m_value;
129                 return true;
130             }
131         }
132 
133         return false;
134     }
135 
keys()136     public Object [] keys ()
137     {
138         final Object [] result = new Object [m_size];
139         int scan = 0;
140 
141         for (int b = 0; b < m_buckets.length; ++ b)
142         {
143             for (Entry entry = m_buckets [b]; entry != null; entry = entry.m_next)
144             {
145                 result [scan ++] = entry.m_key;
146             }
147         }
148 
149         return result;
150     }
151 
152     /**
153      * Updates the table to map 'key' to 'value'. Any existing mapping is overwritten.
154      *
155      * @param key mapping key [may not be null]
156      * @param value mapping value.
157      */
put(final Object key, final int value)158     public void put (final Object key, final int value)
159     {
160         if ($assert.ENABLED) $assert.ASSERT (key != null, "null input: key");
161 
162         Entry currentKeyEntry = null;
163 
164         // detect if 'key' is already in the table [in which case, set 'currentKeyEntry' to point to its entry]:
165 
166         // index into the corresponding hash bucket:
167         final int keyHash = key.hashCode ();
168         int bucketIndex = (keyHash & 0x7FFFFFFF) % m_buckets.length;
169 
170         // traverse the singly-linked list of entries in the bucket:
171         Entry [] buckets = m_buckets;
172         for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
173         {
174             if ((keyHash == entry.m_key.hashCode ()) || entry.m_key.equals (key))
175             {
176                 currentKeyEntry = entry;
177                 break;
178             }
179         }
180 
181         if (currentKeyEntry != null)
182         {
183             // replace the current value:
184 
185             currentKeyEntry.m_value = value;
186         }
187         else
188         {
189             // add a new entry:
190 
191             if (m_size >= m_sizeThreshold) rehash ();
192 
193             buckets = m_buckets;
194             bucketIndex = (keyHash & 0x7FFFFFFF) % buckets.length;
195             final Entry bucketListHead = buckets [bucketIndex];
196             final Entry newEntry = new Entry (key, value, bucketListHead);
197             buckets [bucketIndex] = newEntry;
198 
199             ++ m_size;
200         }
201     }
202 
203     /**
204      * Updates the table to map 'key' to 'value'. Any existing mapping is overwritten.
205      *
206      * @param key mapping key [may not be null]
207      */
remove(final Object key)208     public void remove (final Object key)
209     {
210         if ($assert.ENABLED) $assert.ASSERT (key != null, "null input: key");
211 
212         // index into the corresponding hash bucket:
213         final int keyHash = key.hashCode ();
214         final int bucketIndex = (keyHash  & 0x7FFFFFFF) % m_buckets.length;
215 
216         // traverse the singly-linked list of entries in the bucket:
217         Entry [] buckets = m_buckets;
218         for (Entry entry = buckets [bucketIndex], prev = entry; entry != null; )
219         {
220             final Entry next = entry.m_next;
221 
222             if ((keyHash == entry.m_key.hashCode ()) || entry.m_key.equals (key))
223             {
224                 if (prev == entry)
225                     buckets [bucketIndex] = next;
226                 else
227                     prev.m_next = next;
228 
229                 -- m_size;
230                 break;
231             }
232 
233             prev = entry;
234             entry = next;
235         }
236     }
237 
238 
239     // protected: .............................................................
240 
241     // package: ...............................................................
242 
243 
debugDump(final StringBuffer out)244     void debugDump (final StringBuffer out)
245     {
246         if (out != null)
247         {
248             out.append (super.toString ()); out.append (EOL);
249             out.append ("size = " + m_size + ", bucket table size = " + m_buckets.length + ", load factor = " + m_loadFactor + EOL);
250             out.append ("size threshold = " + m_sizeThreshold + EOL);
251         }
252     }
253 
254     // private: ...............................................................
255 
256 
257     /**
258      * The structure used for chaining colliding keys.
259      */
260     private static final class Entry
261     {
Entry(final Object key, final int value, final Entry next)262         Entry (final Object key, final int value, final Entry next)
263         {
264             m_key = key;
265             m_value = value;
266             m_next = next;
267         }
268 
269         Object m_key;     // reference to the value [never null]
270         int m_value;
271 
272         Entry m_next; // singly-linked list link
273 
274     } // end of nested class
275 
276 
277     /**
278      * Re-hashes the table into a new array of buckets.
279      */
rehash()280     private void rehash ()
281     {
282         // TODO: it is possible to run this method twice, first time using the 2*k+1 prime sequencer for newBucketCount
283         // and then with that value reduced to actually shrink capacity. As it is right now, the bucket table can
284         // only grow in size
285 
286         final Entry [] buckets = m_buckets;
287 
288         final int newBucketCount = (m_buckets.length << 1) + 1;
289         final Entry [] newBuckets = new Entry [newBucketCount];
290 
291         // rehash all entry chains in every bucket:
292         for (int b = 0; b < buckets.length; ++ b)
293         {
294             for (Entry entry = buckets [b]; entry != null; )
295             {
296                 final Entry next = entry.m_next; // remember next pointer because we are going to reuse this entry
297                 final int entryKeyHash = entry.m_key.hashCode () & 0x7FFFFFFF;
298 
299                 // index into the corresponding new hash bucket:
300                 final int newBucketIndex = entryKeyHash % newBucketCount;
301 
302                 final Entry bucketListHead = newBuckets [newBucketIndex];
303                 entry.m_next = bucketListHead;
304                 newBuckets [newBucketIndex] = entry;
305 
306                 entry = next;
307             }
308         }
309 
310 
311         m_sizeThreshold = (int) (newBucketCount * m_loadFactor);
312         m_buckets = newBuckets;
313     }
314 
315 
316     private final float m_loadFactor; // determines the setting of m_sizeThreshold
317 
318     private Entry [] m_buckets; // table of buckets
319     private int m_size; // number of keys in the table, not cleared as of last check
320     private int m_sizeThreshold; // size threshold for rehashing
321 
322     private static final String EOL = System.getProperty ("line.separator", "\n");
323 
324 } // end of class
325 // ----------------------------------------------------------------------------
326 
327