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