KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > vladium > util > ObjectIntMap


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      */

34     public ObjectIntMap ()
35     {
36         this (11, 0.75F);
37     }
38     
39     /**
40      * Equivalent to <CODE>IntObjectMap(capacity, 0.75F)</CODE>.
41      */

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      */

53     public ObjectIntMap (int initialCapacity, final float loadFactor)
54     {
55         if (initialCapacity < 0) throw new IllegalArgumentException JavaDoc ("negative input: initialCapacity [" + initialCapacity + "]");
56         if ((loadFactor <= 0.0) || (loadFactor >= 1.0 + 1.0E-6))
57             throw new IllegalArgumentException JavaDoc ("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      */

70     public String JavaDoc toString ()
71     {
72         final StringBuffer JavaDoc s = new StringBuffer JavaDoc ();
73         debugDump (s);
74         
75         return s.toString ();
76     }
77     
78     /**
79      * Returns the number of key-value mappings in this map.
80      */

81     public int size ()
82     {
83         return m_size;
84     }
85
86     public boolean contains (final Object JavaDoc 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      */

114     public boolean get (final Object JavaDoc 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     
136     public Object JavaDoc [] keys ()
137     {
138         final Object JavaDoc [] result = new Object JavaDoc [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      */

158     public void put (final Object JavaDoc 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      */

208     public void remove (final Object JavaDoc 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     
244     void debugDump (final StringBuffer JavaDoc 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     {
262         Entry (final Object JavaDoc key, final int value, final Entry next)
263         {
264             m_key = key;
265             m_value = value;
266             m_next = next;
267         }
268         
269         Object JavaDoc 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      */

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 JavaDoc EOL = System.getProperty ("line.separator", "\n");
323     
324 } // end of class
325
// ----------------------------------------------------------------------------
326

327
Popular Tags