• 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: IntObjectMap.java,v 1.1.1.1 2004/05/09 16:57:53 vlad_r Exp $
8  */
9 package com.vladium.util;
10 
11 import java.io.Serializable;
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 Vlad Roubtsov, (C) 2001
23  */
24 public
25 final class IntObjectMap implements Serializable
26 {
27     // public: ................................................................
28 
29     /**
30      * Equivalent to <CODE>IntObjectMap(11, 0.75F)</CODE>.
31      */
IntObjectMap()32     public IntObjectMap ()
33     {
34         this (11, 0.75F);
35     }
36 
37     /**
38      * Equivalent to <CODE>IntObjectMap(capacity, 0.75F)</CODE>.
39      */
IntObjectMap(final int initialCapacity)40     public IntObjectMap (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      */
IntObjectMap(int initialCapacity, final float loadFactor)51     public IntObjectMap (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)
94                 return true;
95         }
96 
97         return false;
98     }
99 
100     /**
101      * Returns the value that is mapped to a given 'key'. Returns
102      * null if (a) this key has never been mapped or (b) it has been
103      * mapped to a null value.
104      *
105      * @param key mapping key
106      *
107      * @return Object value mapping for 'key' [can be null].
108      */
get(final int key)109     public Object get (final int key)
110     {
111         // index into the corresponding hash bucket:
112         final Entry [] buckets = m_buckets;
113         final int bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
114 
115         // traverse the singly-linked list of entries in the bucket:
116         for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
117         {
118             if (key == entry.m_key)
119                 return entry.m_value;
120         }
121 
122         return null;
123     }
124 
keys()125     public int [] keys ()
126     {
127         if (m_size == 0)
128             return IConstants.EMPTY_INT_ARRAY;
129         else
130         {
131             final int [] result = new int [m_size];
132             int scan = 0;
133 
134             for (int b = 0; b < m_buckets.length; ++ b)
135             {
136                 for (Entry entry = m_buckets [b]; entry != null; entry = entry.m_next)
137                 {
138                     result [scan ++] = entry.m_key;
139                 }
140             }
141 
142             return result;
143         }
144     }
145 
146     /**
147      * Updates the table to map 'key' to 'value'. Any existing mapping is overwritten.
148      *
149      * @param key mapping key
150      * @param value mapping value [can be null].
151      *
152      * @return Object previous value mapping for 'key' [can be null]
153      */
put(final int key, final Object value)154     public Object put (final int key, final Object value)
155     {
156         Entry currentKeyEntry = null;
157 
158         // detect if 'key' is already in the table [in which case, set 'currentKeyEntry' to point to its entry]:
159 
160         // index into the corresponding hash bucket:
161         int bucketIndex = (key & 0x7FFFFFFF) % m_buckets.length;
162 
163         // traverse the singly-linked list of entries in the bucket:
164         Entry [] buckets = m_buckets;
165         for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
166         {
167             if (key == entry.m_key)
168             {
169                 currentKeyEntry = entry;
170                 break;
171             }
172         }
173 
174         if (currentKeyEntry != null)
175         {
176             // replace the current value:
177 
178             final Object currentKeyValue = currentKeyEntry.m_value;
179             currentKeyEntry.m_value = value;
180 
181             return currentKeyValue;
182         }
183         else
184         {
185             // add a new entry:
186 
187             if (m_size >= m_sizeThreshold) rehash ();
188 
189             buckets = m_buckets;
190             bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
191             final Entry bucketListHead = buckets [bucketIndex];
192             final Entry newEntry = new Entry (key, value, bucketListHead);
193             buckets [bucketIndex] = newEntry;
194 
195             ++ m_size;
196 
197             return null;
198         }
199     }
200 
201     // protected: .............................................................
202 
203     // package: ...............................................................
204 
205 
debugDump(final StringBuffer out)206     void debugDump (final StringBuffer out)
207     {
208         if (out != null)
209         {
210             out.append (super.toString ()); out.append (EOL);
211             out.append ("size = " + m_size + ", bucket table size = " + m_buckets.length + ", load factor = " + m_loadFactor + EOL);
212             out.append ("size threshold = " + m_sizeThreshold + EOL);
213         }
214     }
215 
216     // private: ...............................................................
217 
218 
219     /**
220      * The structure used for chaining colliding keys.
221      */
222     private static final class Entry implements Serializable
223     {
Entry(final int key, final Object value, final Entry next)224         Entry (final int key, final Object value, final Entry next)
225         {
226             m_key = key;
227             m_value = value;
228             m_next = next;
229         }
230 
231         Object m_value;           // reference to the value [never null]
232         final int m_key;
233 
234         Entry m_next; // singly-linked list link
235 
236     } // end of nested class
237 
238 
239     /**
240      * Re-hashes the table into a new array of buckets.
241      */
rehash()242     private void rehash ()
243     {
244         // TODO: it is possible to run this method twice, first time using the 2*k+1 prime sequencer for newBucketCount
245         // and then with that value reduced to actually shrink capacity. As it is right now, the bucket table can
246         // only grow in size
247 
248         final Entry [] buckets = m_buckets;
249 
250         final int newBucketCount = (m_buckets.length << 1) + 1;
251         final Entry [] newBuckets = new Entry [newBucketCount];
252 
253         // rehash all entry chains in every bucket:
254         for (int b = 0; b < buckets.length; ++ b)
255         {
256             for (Entry entry = buckets [b]; entry != null; )
257             {
258                 final Entry next = entry.m_next; // remember next pointer because we are going to reuse this entry
259                 final int entryKey = entry.m_key;
260 
261                 // index into the corresponding new hash bucket:
262                 final int newBucketIndex = (entryKey & 0x7FFFFFFF) % newBucketCount;
263 
264                 final Entry bucketListHead = newBuckets [newBucketIndex];
265                 entry.m_next = bucketListHead;
266                 newBuckets [newBucketIndex] = entry;
267 
268                 entry = next;
269             }
270         }
271 
272 
273         m_sizeThreshold = (int) (newBucketCount * m_loadFactor);
274         m_buckets = newBuckets;
275     }
276 
277 
278     private final float m_loadFactor; // determines the setting of m_sizeThreshold
279 
280     private Entry [] m_buckets; // table of buckets
281     private int m_size; // number of keys in the table, not cleared as of last check
282     private int m_sizeThreshold; // size threshold for rehashing
283 
284     private static final String EOL = System.getProperty ("line.separator", "\n");
285 
286 } // end of class
287 // ----------------------------------------------------------------------------
288 
289