31 |
|
|
32 |
/** |
/** |
33 |
* This class provides a hashtable-backed implementation of the |
* This class provides a hashtable-backed implementation of the |
34 |
* Map interface. Unlike HashMap, it uses object identity to |
* Map interface, but uses object identity to do its hashing. In fact, |
35 |
* do its hashing. Also, it uses a linear-probe hash table. |
* it uses object identity for comparing values, as well. It uses a |
36 |
|
* linear-probe hash table, which may have faster performance |
37 |
|
* than the chaining employed by HashMap. |
38 |
|
* <p> |
39 |
|
* |
40 |
|
* <em>WARNING: This is not a general purpose map. Because it uses |
41 |
|
* System.identityHashCode and ==, instead of hashCode and equals, for |
42 |
|
* comparison, it violated Map's general contract, and may cause |
43 |
|
* undefined behavior when compared to other maps which are not |
44 |
|
* IdentityHashMaps. This is designed only for the rare cases when |
45 |
|
* identity semantics are needed.</em> An example use is |
46 |
|
* topology-preserving graph transformations, such as deep cloning, |
47 |
|
* or as proxy object mapping such as in debugging. |
48 |
|
* <p> |
49 |
|
* |
50 |
|
* This map permits <code>null</code> keys and values, and does not |
51 |
|
* guarantee that elements will stay in the same order over time. The |
52 |
|
* basic operations (<code>get</code> and <code>put</code>) take |
53 |
|
* constant time, provided System.identityHashCode is decent. You can |
54 |
|
* tune the behavior by specifying the expected maximum size. As more |
55 |
|
* elements are added, the map may need to allocate a larger table, |
56 |
|
* which can be expensive. |
57 |
|
* <p> |
58 |
|
* |
59 |
|
* This implementation is unsynchronized. If you want multi-thread |
60 |
|
* access to be consistent, you must synchronize it, perhaps by using |
61 |
|
* <code>Collections.synchronizedMap(new IdentityHashMap(...));</code>. |
62 |
|
* The iterators are <i>fail-fast</i>, meaning that a structural modification |
63 |
|
* made to the map outside of an iterator's remove method cause the |
64 |
|
* iterator, and in the case of the entrySet, the Map.Entry, to |
65 |
|
* fail with a {@link ConcurrentModificationException}. |
66 |
* |
* |
67 |
* @author Tom Tromey <tromey@redhat.com> |
* @author Tom Tromey <tromey@redhat.com> |
68 |
|
* @author Eric Blake <ebb9@email.byu.edu> |
69 |
|
* @see System#identityHashCode(Object) |
70 |
|
* @see Collection |
71 |
|
* @see Map |
72 |
|
* @see HashMap |
73 |
|
* @see TreeMap |
74 |
|
* @see LinkedHashMap |
75 |
|
* @see WeakHashMap |
76 |
* @since 1.4 |
* @since 1.4 |
77 |
|
* @status updated to 1.4 |
78 |
*/ |
*/ |
79 |
public class IdentityHashMap extends AbstractMap |
public class IdentityHashMap extends AbstractMap |
80 |
implements Map, Serializable, Cloneable |
implements Map, Serializable, Cloneable |
81 |
{ |
{ |
82 |
|
/** The default capacity. */ |
83 |
private static final int DEFAULT_CAPACITY = 21; |
private static final int DEFAULT_CAPACITY = 21; |
84 |
|
|
85 |
/** Create a new IdentityHashMap with the default capacity (21 |
/** |
86 |
* entries). |
* This object is used to mark deleted items. Package visible for use by |
87 |
|
* nested classes. |
88 |
|
*/ |
89 |
|
static final Object tombstone = new Object(); |
90 |
|
|
91 |
|
/** |
92 |
|
* This object is used to mark empty slots. We need this because |
93 |
|
* using null is ambiguous. Package visible for use by nested classes. |
94 |
|
*/ |
95 |
|
static final Object emptyslot = new Object(); |
96 |
|
|
97 |
|
/** |
98 |
|
* Compatible with JDK 1.4. |
99 |
|
*/ |
100 |
|
private static final long serialVersionUID = 8188218128353913216L; |
101 |
|
|
102 |
|
/** |
103 |
|
* The number of mappings in the table. Package visible for use by nested |
104 |
|
* classes. |
105 |
|
* @serial |
106 |
|
*/ |
107 |
|
int size; |
108 |
|
|
109 |
|
/** |
110 |
|
* The table itself. Package visible for use by nested classes. |
111 |
|
*/ |
112 |
|
transient Object[] table; |
113 |
|
|
114 |
|
/** |
115 |
|
* The number of structural modifications made so far. Package visible for |
116 |
|
* use by nested classes. |
117 |
|
*/ |
118 |
|
transient int modCount; |
119 |
|
|
120 |
|
/** |
121 |
|
* The cache for {@link #entrySet()}. |
122 |
|
*/ |
123 |
|
private transient Set entries; |
124 |
|
|
125 |
|
/** |
126 |
|
* The threshold for rehashing, which is 75% of (table.length / 2). |
127 |
|
*/ |
128 |
|
private transient int threshold; |
129 |
|
|
130 |
|
/** |
131 |
|
* Create a new IdentityHashMap with the default capacity (21 entries). |
132 |
*/ |
*/ |
133 |
public IdentityHashMap () |
public IdentityHashMap() |
134 |
{ |
{ |
135 |
this (DEFAULT_CAPACITY); |
this(DEFAULT_CAPACITY); |
136 |
} |
} |
137 |
|
|
138 |
/** Create a new IdentityHashMap with the indicated number of |
/** |
139 |
|
* Create a new IdentityHashMap with the indicated number of |
140 |
* entries. If the number of elements added to this hash map |
* entries. If the number of elements added to this hash map |
141 |
* exceeds this maximum, the map will grow itself; however, that |
* exceeds this maximum, the map will grow itself; however, that |
142 |
* incurs a performance penalty. |
* incurs a performance penalty. |
143 |
* @param max Initial size |
* |
144 |
|
* @param max initial size |
145 |
|
* @throws IllegalArgumentException if max is negative |
146 |
*/ |
*/ |
147 |
public IdentityHashMap (int max) |
public IdentityHashMap(int max) |
148 |
{ |
{ |
149 |
if (max < 0) |
if (max < 0) |
150 |
throw new IllegalArgumentException (); |
throw new IllegalArgumentException(); |
151 |
|
// Need at least two slots, or hash() will break. |
152 |
|
if (max < 2) |
153 |
|
max = 2; |
154 |
table = new Object[2 * max]; |
table = new Object[2 * max]; |
155 |
Arrays.fill (table, emptyslot); |
Arrays.fill(table, emptyslot); |
156 |
size = 0; |
// This is automatically set. |
157 |
|
// size = 0; |
158 |
|
threshold = max / 4 * 3; |
159 |
} |
} |
160 |
|
|
161 |
/** Create a new IdentityHashMap whose contents are taken from the |
/** |
162 |
|
* Create a new IdentityHashMap whose contents are taken from the |
163 |
* given Map. |
* given Map. |
164 |
* @param m The map whose elements are to be put in this map. |
* |
165 |
|
* @param m The map whose elements are to be put in this map |
166 |
|
* @throws NullPointerException if m is null |
167 |
*/ |
*/ |
168 |
public IdentityHashMap (Map m) |
public IdentityHashMap(Map m) |
169 |
{ |
{ |
170 |
int len = 2 * Math.max (m.size (), DEFAULT_CAPACITY); |
this(Math.max(m.size() * 2, DEFAULT_CAPACITY)); |
171 |
table = new Object[len]; |
putAll(m); |
|
Arrays.fill (table, emptyslot); |
|
|
putAll (m); |
|
172 |
} |
} |
173 |
|
|
174 |
public void clear () |
/** |
175 |
|
* Remove all mappings from this map. |
176 |
|
*/ |
177 |
|
public void clear() |
178 |
{ |
{ |
179 |
Arrays.fill (table, emptyslot); |
if (size != 0) |
180 |
size = 0; |
{ |
181 |
|
modCount++; |
182 |
|
Arrays.fill(table, emptyslot); |
183 |
|
size = 0; |
184 |
|
} |
185 |
} |
} |
186 |
|
|
187 |
/** |
/** |
188 |
* Creates a shallow copy where keys and values are not cloned. |
* Creates a shallow copy where keys and values are not cloned. |
189 |
*/ |
*/ |
190 |
public Object clone () |
public Object clone() |
191 |
{ |
{ |
192 |
try |
try |
193 |
{ |
{ |
194 |
IdentityHashMap copy = (IdentityHashMap) super.clone (); |
IdentityHashMap copy = (IdentityHashMap) super.clone(); |
195 |
copy.table = (Object[]) table.clone (); |
copy.table = (Object[]) table.clone(); |
196 |
return copy; |
copy.entries = null; // invalidate the cache |
197 |
|
return copy; |
198 |
} |
} |
199 |
catch (CloneNotSupportedException e) |
catch (CloneNotSupportedException e) |
200 |
{ |
{ |
201 |
// Can't happen. |
// Can't happen. |
202 |
return null; |
return null; |
203 |
} |
} |
204 |
} |
} |
205 |
|
|
206 |
public boolean containsKey (Object key) |
/** |
207 |
|
* Tests whether the specified key is in this map. Unlike normal Maps, |
208 |
|
* this test uses <code>entry == key</code> instead of |
209 |
|
* <code>entry == null ? key == null : entry.equals(key)</code>. |
210 |
|
* |
211 |
|
* @param key the key to look for |
212 |
|
* @return true if the key is contained in the map |
213 |
|
* @see #containsValue(Object) |
214 |
|
* @see #get(Object) |
215 |
|
*/ |
216 |
|
public boolean containsKey(Object key) |
217 |
{ |
{ |
218 |
int h = getHash (key); |
return key == table[hash(key)]; |
|
int save = h; |
|
|
while (true) |
|
|
{ |
|
|
if (table[h] == key) |
|
|
return true; |
|
|
if (table[h] == emptyslot) |
|
|
return false; |
|
|
h += 2; |
|
|
if (h >= table.length) |
|
|
h = 0; |
|
|
if (h == save) |
|
|
return false; |
|
|
} |
|
219 |
} |
} |
220 |
|
|
221 |
public boolean containsValue (Object value) |
/** |
222 |
|
* Returns true if this HashMap contains the value. Unlike normal maps, |
223 |
|
* this test uses <code>entry == value</code> instead of |
224 |
|
* <code>entry == null ? value == null : entry.equals(value)</code>. |
225 |
|
* |
226 |
|
* @param value the value to search for in this HashMap |
227 |
|
* @return true if at least one key maps to the value |
228 |
|
* @see #containsKey(Object) |
229 |
|
*/ |
230 |
|
public boolean containsValue(Object value) |
231 |
{ |
{ |
232 |
for (int i = 1; i < table.length; i += 2) |
for (int i = table.length - 1; i > 0; i -= 2) |
233 |
if (table[i] == value) |
if (table[i] == value) |
234 |
return true; |
return true; |
235 |
return false; |
return false; |
236 |
} |
} |
237 |
|
|
238 |
public Set entrySet () |
/** |
239 |
|
* Returns a "set view" of this Map's entries. The set is backed by |
240 |
|
* the Map, so changes in one show up in the other. The set supports |
241 |
|
* element removal, but not element addition. |
242 |
|
* <p> |
243 |
|
* |
244 |
|
* <em>The semantics of this set, and of its contained entries, are |
245 |
|
* different from the contract of Set and Map.Entry in order to make |
246 |
|
* IdentityHashMap work. This means that while you can compare these |
247 |
|
* objects between IdentityHashMaps, comparing them with regular sets |
248 |
|
* or entries is likely to have undefined behavior.</em> The entries |
249 |
|
* in this set are reference-based, rather than the normal object |
250 |
|
* equality. Therefore, <code>e1.equals(e2)</code> returns |
251 |
|
* <code>e1.getKey() == e2.getKey() && e1.getValue() == e2.getValue()</code>, |
252 |
|
* and <code>e.hashCode()</code> returns |
253 |
|
* <code>System.identityHashCode(e.getKey()) ^ |
254 |
|
* System.identityHashCode(e.getValue())</code>. |
255 |
|
* <p> |
256 |
|
* |
257 |
|
* Note that the iterators for all three views, from keySet(), entrySet(), |
258 |
|
* and values(), traverse the Map in the same sequence. |
259 |
|
* |
260 |
|
* @return a set view of the entries |
261 |
|
* @see #keySet() |
262 |
|
* @see #values() |
263 |
|
* @see Map.Entry |
264 |
|
*/ |
265 |
|
public Set entrySet() |
266 |
{ |
{ |
267 |
return new AbstractSet () |
if (entries == null) |
268 |
{ |
entries = new AbstractSet() |
|
public int size () |
|
|
{ |
|
|
return size; |
|
|
} |
|
|
|
|
|
public Iterator iterator () |
|
|
{ |
|
|
return new IdentityIterator (IdentityIterator.ENTRIES); |
|
|
} |
|
|
|
|
|
public void clear () |
|
269 |
{ |
{ |
270 |
IdentityHashMap.this.clear (); |
public int size() |
271 |
} |
{ |
272 |
|
return size; |
273 |
|
} |
274 |
|
|
275 |
|
public Iterator iterator() |
276 |
|
{ |
277 |
|
return new IdentityIterator(ENTRIES); |
278 |
|
} |
279 |
|
|
280 |
|
public void clear() |
281 |
|
{ |
282 |
|
IdentityHashMap.this.clear(); |
283 |
|
} |
284 |
|
|
285 |
|
public boolean contains(Object o) |
286 |
|
{ |
287 |
|
if (! (o instanceof Map.Entry)) |
288 |
|
return false; |
289 |
|
Map.Entry m = (Map.Entry) o; |
290 |
|
return m.getValue() == table[hash(m.getKey()) + 1]; |
291 |
|
} |
292 |
|
|
293 |
|
public int hashCode() |
294 |
|
{ |
295 |
|
return IdentityHashMap.this.hashCode(); |
296 |
|
} |
297 |
|
|
298 |
|
public boolean remove(Object o) |
299 |
|
{ |
300 |
|
if (! (o instanceof Map.Entry)) |
301 |
|
return false; |
302 |
|
Object key = ((Map.Entry) o).getKey(); |
303 |
|
int h = hash(key); |
304 |
|
if (table[h] == key) |
305 |
|
{ |
306 |
|
size--; |
307 |
|
modCount++; |
308 |
|
table[h] = tombstone; |
309 |
|
table[h + 1] = tombstone; |
310 |
|
return true; |
311 |
|
} |
312 |
|
return false; |
313 |
|
} |
314 |
|
}; |
315 |
|
return entries; |
316 |
|
} |
317 |
|
|
318 |
public boolean contains (Object o) |
/** |
319 |
{ |
* Compares two maps for equality. This returns true only if both maps |
320 |
if (! (o instanceof Map.Entry)) |
* have the same reference-identity comparisons. While this returns |
321 |
return false; |
* <code>this.entrySet().equals(m.entrySet())</code> as specified by Map, |
322 |
Map.Entry m = (Map.Entry) o; |
* this will not work with normal maps, since the entry set compares |
323 |
return (IdentityHashMap.this.containsKey (m.getKey ()) |
* with == instead of .equals. |
324 |
&& IdentityHashMap.this.get (m.getKey ()) == m.getValue ()); |
* |
325 |
} |
* @param o the object to compare to |
326 |
|
* @return true if it is equal |
327 |
|
*/ |
328 |
|
public boolean equals(Object o) |
329 |
|
{ |
330 |
|
// Why did Sun specify this one? The superclass does the right thing. |
331 |
|
return super.equals(o); |
332 |
|
} |
333 |
|
|
334 |
public boolean remove (Object o) |
/** |
335 |
{ |
* Return the value in this Map associated with the supplied key, |
336 |
if (! (o instanceof Map.Entry)) |
* or <pre>null</pre> if the key maps to nothing. NOTE: Since the value |
337 |
return false; |
* could also be null, you must use containsKey to see if this key |
338 |
Map.Entry m = (Map.Entry) o; |
* actually maps to something. Unlike normal maps, this tests for the key |
339 |
if (IdentityHashMap.this.containsKey (m.getKey ()) |
* with <code>entry == key</code> instead of |
340 |
&& IdentityHashMap.this.get (m.getKey ()) == m.getValue ()) |
* <code>entry == null ? key == null : entry.equals(key)</code>. |
341 |
{ |
* |
342 |
int oldsize = size; |
* @param key the key for which to fetch an associated value |
343 |
IdentityHashMap.this.remove (m.getKey ()); |
* @return what the key maps to, if present |
344 |
return oldsize != size; |
* @see #put(Object, Object) |
345 |
} |
* @see #containsKey(Object) |
346 |
return false; |
*/ |
347 |
} |
public Object get(Object key) |
348 |
}; |
{ |
349 |
|
int h = hash(key); |
350 |
|
return table[h] == key ? table[h + 1] : null; |
351 |
} |
} |
352 |
|
|
353 |
public Object get (Object key) |
/** |
354 |
|
* Returns the hashcode of this map. This guarantees that two |
355 |
|
* IdentityHashMaps that compare with equals() will have the same hash code, |
356 |
|
* but may break with comparison to normal maps since it uses |
357 |
|
* System.identityHashCode() instead of hashCode(). |
358 |
|
* |
359 |
|
* @return the hash code |
360 |
|
*/ |
361 |
|
public int hashCode() |
362 |
{ |
{ |
363 |
int h = getHash (key); |
int hash = 0; |
364 |
int save = h; |
for (int i = table.length - 2; i >= 0; i -= 2) |
|
while (true) |
|
365 |
{ |
{ |
366 |
if (table[h] == key) |
Object key = table[i]; |
367 |
return table[h + 1]; |
if (key == emptyslot || key == tombstone) |
368 |
if (table[h] == emptyslot) |
continue; |
369 |
return null; |
hash += (System.identityHashCode(key) |
370 |
h += 2; |
^ System.identityHashCode(table[i + 1])); |
|
if (h >= table.length) |
|
|
h = 0; |
|
|
if (h == save) |
|
|
return null; |
|
371 |
} |
} |
372 |
|
return hash; |
373 |
} |
} |
374 |
|
|
375 |
public boolean isEmpty () |
/** |
376 |
|
* Returns true if there are no key-value mappings currently in this Map |
377 |
|
* @return <code>size() == 0</code> |
378 |
|
*/ |
379 |
|
public boolean isEmpty() |
380 |
{ |
{ |
381 |
return size == 0; |
return size == 0; |
382 |
} |
} |
383 |
|
|
384 |
public Set keySet () |
/** |
385 |
|
* Returns a "set view" of this Map's keys. The set is backed by the |
386 |
|
* Map, so changes in one show up in the other. The set supports |
387 |
|
* element removal, but not element addition. |
388 |
|
* <p> |
389 |
|
* |
390 |
|
* <em>The semantics of this set are different from the contract of Set |
391 |
|
* in order to make IdentityHashMap work. This means that while you can |
392 |
|
* compare these objects between IdentityHashMaps, comparing them with |
393 |
|
* regular sets is likely to have undefined behavior.</em> The hashCode |
394 |
|
* of the set is the sum of the identity hash codes, instead of the |
395 |
|
* regular hashCodes, and equality is determined by reference instead |
396 |
|
* of by the equals method. |
397 |
|
* <p> |
398 |
|
* |
399 |
|
* @return a set view of the keys |
400 |
|
* @see #values() |
401 |
|
* @see #entrySet() |
402 |
|
*/ |
403 |
|
public Set keySet() |
404 |
{ |
{ |
405 |
return new AbstractSet () |
if (keys == null) |
406 |
{ |
keys = new AbstractSet() |
|
public int size () |
|
|
{ |
|
|
return size; |
|
|
} |
|
|
|
|
|
public Iterator iterator () |
|
|
{ |
|
|
return new IdentityIterator (IdentityIterator.KEYS); |
|
|
} |
|
|
|
|
|
public void clear () |
|
|
{ |
|
|
IdentityHashMap.this.clear (); |
|
|
} |
|
|
|
|
|
public boolean contains (Object o) |
|
407 |
{ |
{ |
408 |
return IdentityHashMap.this.containsKey (o); |
public int size() |
409 |
} |
{ |
410 |
|
return size; |
411 |
public boolean remove (Object o) |
} |
412 |
{ |
|
413 |
int oldsize = size; |
public Iterator iterator() |
414 |
IdentityHashMap.this.remove (o); |
{ |
415 |
return oldsize != size; |
return new IdentityIterator(KEYS); |
416 |
} |
} |
417 |
}; |
|
418 |
|
public void clear() |
419 |
|
{ |
420 |
|
IdentityHashMap.this.clear(); |
421 |
|
} |
422 |
|
|
423 |
|
public boolean contains(Object o) |
424 |
|
{ |
425 |
|
return containsKey(o); |
426 |
|
} |
427 |
|
|
428 |
|
public int hashCode() |
429 |
|
{ |
430 |
|
int hash = 0; |
431 |
|
for (int i = table.length - 2; i >= 0; i -= 2) |
432 |
|
{ |
433 |
|
Object key = table[i]; |
434 |
|
if (key == emptyslot || key == tombstone) |
435 |
|
continue; |
436 |
|
hash += System.identityHashCode(key); |
437 |
|
} |
438 |
|
return hash; |
439 |
|
|
440 |
|
} |
441 |
|
|
442 |
|
public boolean remove(Object o) |
443 |
|
{ |
444 |
|
int h = hash(o); |
445 |
|
if (table[h] == o) |
446 |
|
{ |
447 |
|
size--; |
448 |
|
modCount++; |
449 |
|
table[h] = tombstone; |
450 |
|
table[h + 1] = tombstone; |
451 |
|
return true; |
452 |
|
} |
453 |
|
return false; |
454 |
|
} |
455 |
|
}; |
456 |
|
return keys; |
457 |
} |
} |
458 |
|
|
459 |
public Object put (Object key, Object value) |
/** |
460 |
|
* Puts the supplied value into the Map, mapped by the supplied key. |
461 |
|
* The value may be retrieved by any object which <code>equals()</code> |
462 |
|
* this key. NOTE: Since the prior value could also be null, you must |
463 |
|
* first use containsKey if you want to see if you are replacing the |
464 |
|
* key's mapping. Unlike normal maps, this tests for the key |
465 |
|
* with <code>entry == key</code> instead of |
466 |
|
* <code>entry == null ? key == null : entry.equals(key)</code>. |
467 |
|
* |
468 |
|
* @param key the key used to locate the value |
469 |
|
* @param value the value to be stored in the HashMap |
470 |
|
* @return the prior mapping of the key, or null if there was none |
471 |
|
* @see #get(Object) |
472 |
|
*/ |
473 |
|
public Object put(Object key, Object value) |
474 |
{ |
{ |
475 |
// Rehash if the load factor is too high. We use a factor of 1.5 |
// Rehash if the load factor is too high. |
476 |
// -- the division by 2 is implicit on both sides. |
if (size > threshold) |
|
if (size * 3 > table.length) |
|
|
{ |
|
|
Object[] old = table; |
|
|
table = new Object[old.length * 2]; |
|
|
Arrays.fill (table, emptyslot); |
|
|
size = 0; |
|
|
for (int i = 0; i < old.length; i += 2) |
|
|
{ |
|
|
if (old[i] != tombstone && old[i] != emptyslot) |
|
|
{ |
|
|
// Just use put. This isn't very efficient, but it is |
|
|
// ok. |
|
|
put (old[i], old[i + 1]); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
int h = getHash (key); |
|
|
int save = h; |
|
|
int del = -1; |
|
|
while (true) |
|
477 |
{ |
{ |
478 |
if (table[h] == key) |
Object[] old = table; |
479 |
{ |
// This isn't necessarily prime, but it is an odd number of key/value |
480 |
Object r = table[h + 1]; |
// slots, which has a higher probability of fewer collisions. |
481 |
table[h + 1] = value; |
table = new Object[old.length * 2 + 2]; |
482 |
return r; |
Arrays.fill(table, emptyslot); |
483 |
} |
size = 0; |
484 |
else if (table[h] == tombstone && del == -1) |
threshold = table.length / 4 * 3; |
485 |
del = h; |
|
486 |
else if (table[h] == emptyslot) |
for (int i = old.length - 2; i >= 0; i -= 2) |
487 |
{ |
{ |
488 |
if (del == -1) |
Object oldkey = old[i]; |
489 |
del = h; |
if (oldkey != tombstone && oldkey != emptyslot) |
490 |
break; |
// Just use put. This isn't very efficient, but it is ok. |
491 |
} |
put(oldkey, old[i + 1]); |
492 |
h += 2; |
} |
493 |
if (h >= table.length) |
} |
494 |
h = 0; |
|
495 |
if (h == save) |
int h = hash(key); |
496 |
break; |
if (table[h] == key) |
497 |
} |
{ |
498 |
|
Object r = table[h + 1]; |
499 |
if (del != -1) |
table[h + 1] = value; |
500 |
{ |
return r; |
501 |
table[del] = key; |
} |
502 |
table[del + 1] = value; |
|
503 |
++size; |
// At this point, we add a new mapping. |
504 |
return null; |
size++; |
505 |
} |
modCount++; |
506 |
|
table[h] = key; |
507 |
// This is an error. |
table[h + 1] = value; |
508 |
return null; |
return null; |
509 |
} |
} |
510 |
|
|
511 |
public Object remove (Object key) |
/** |
512 |
|
* Copies all of the mappings from the specified map to this. If a key |
513 |
|
* is already in this map, its value is replaced. |
514 |
|
* |
515 |
|
* @param m the map to copy |
516 |
|
* @throws NullPointerException if m is null |
517 |
|
*/ |
518 |
|
public void putAll(Map m) |
519 |
{ |
{ |
520 |
int h = getHash (key); |
// Why did Sun specify this one? The superclass does the right thing. |
521 |
int save = h; |
super.putAll(m); |
522 |
while (true) |
} |
523 |
|
|
524 |
|
/** |
525 |
|
* Removes from the HashMap and returns the value which is mapped by the |
526 |
|
* supplied key. If the key maps to nothing, then the HashMap remains |
527 |
|
* unchanged, and <pre>null</pre> is returned. NOTE: Since the value |
528 |
|
* could also be null, you must use containsKey to see if you are |
529 |
|
* actually removing a mapping. Unlike normal maps, this tests for the |
530 |
|
* key with <code>entry == key</code> instead of |
531 |
|
* <code>entry == null ? key == null : entry.equals(key)</code>. |
532 |
|
* |
533 |
|
* @param key the key used to locate the value to remove |
534 |
|
* @return whatever the key mapped to, if present |
535 |
|
*/ |
536 |
|
public Object remove(Object key) |
537 |
|
{ |
538 |
|
int h = hash(key); |
539 |
|
if (table[h] == key) |
540 |
{ |
{ |
541 |
if (table[h] == key) |
size--; |
542 |
{ |
modCount++; |
543 |
Object r = table[h + 1]; |
Object r = table[h + 1]; |
544 |
table[h] = tombstone; |
table[h] = tombstone; |
545 |
table[h + 1] = tombstone; |
table[h + 1] = tombstone; |
546 |
--size; |
return r; |
|
return r; |
|
|
} |
|
|
h += 2; |
|
|
if (h >= table.length) |
|
|
h = 0; |
|
|
if (h == save) |
|
|
break; |
|
547 |
} |
} |
|
|
|
548 |
return null; |
return null; |
549 |
} |
} |
550 |
|
|
551 |
public int size () |
/** |
552 |
|
* Returns the number of kay-value mappings currently in this Map |
553 |
|
* @return the size |
554 |
|
*/ |
555 |
|
public int size() |
556 |
{ |
{ |
557 |
return size; |
return size; |
558 |
} |
} |
559 |
|
|
560 |
public Collection values () |
/** |
561 |
|
* Returns a "collection view" (or "bag view") of this Map's values. |
562 |
|
* The collection is backed by the Map, so changes in one show up |
563 |
|
* in the other. The collection supports element removal, but not element |
564 |
|
* addition. |
565 |
|
* <p> |
566 |
|
* |
567 |
|
* <em>The semantics of this set are different from the contract of |
568 |
|
* Collection in order to make IdentityHashMap work. This means that |
569 |
|
* while you can compare these objects between IdentityHashMaps, comparing |
570 |
|
* them with regular sets is likely to have undefined behavior.</em> |
571 |
|
* Likewise, contains and remove go by == instead of equals(). |
572 |
|
* <p> |
573 |
|
* |
574 |
|
* @return a bag view of the values |
575 |
|
* @see #keySet() |
576 |
|
* @see #entrySet() |
577 |
|
*/ |
578 |
|
public Collection values() |
579 |
{ |
{ |
580 |
return new AbstractCollection () |
if (values == null) |
581 |
{ |
values = new AbstractCollection() |
|
public int size () |
|
582 |
{ |
{ |
583 |
return size; |
public int size() |
584 |
} |
{ |
585 |
|
return size; |
586 |
|
} |
587 |
|
|
588 |
|
public Iterator iterator() |
589 |
|
{ |
590 |
|
return new IdentityIterator(VALUES); |
591 |
|
} |
592 |
|
|
593 |
|
public void clear() |
594 |
|
{ |
595 |
|
IdentityHashMap.this.clear(); |
596 |
|
} |
597 |
|
|
598 |
|
public boolean remove(Object o) |
599 |
|
{ |
600 |
|
for (int i = table.length - 1; i > 0; i -= 2) |
601 |
|
if (table[i] == o) |
602 |
|
{ |
603 |
|
table[i - 1] = tombstone; |
604 |
|
table[i] = tombstone; |
605 |
|
size--; |
606 |
|
modCount++; |
607 |
|
return true; |
608 |
|
} |
609 |
|
return false; |
610 |
|
} |
611 |
|
}; |
612 |
|
return values; |
613 |
|
} |
614 |
|
|
615 |
public Iterator iterator () |
/** |
616 |
{ |
* Helper method which computes the hash code, then traverses the table |
617 |
return new IdentityIterator (IdentityIterator.VALUES); |
* until it finds the key, or the spot where the key would go. |
618 |
} |
* |
619 |
|
* @param key the key to check |
620 |
|
* @return the index where the key belongs |
621 |
|
* @see #IdentityHashMap(int) |
622 |
|
* @see #put(Object, Object) |
623 |
|
*/ |
624 |
|
// Package visible for use by nested classes. |
625 |
|
int hash(Object key) |
626 |
|
{ |
627 |
|
// Implementation note: it is feasible for the table to have no |
628 |
|
// emptyslots, if it is full with entries and tombstones, so we must |
629 |
|
// remember where we started. If we encounter the key or an emptyslot, |
630 |
|
// we are done. If we encounter a tombstone, the key may still be in |
631 |
|
// the array. If we don't encounter the key, we use the first emptyslot |
632 |
|
// or tombstone we encountered as the location where the key would go. |
633 |
|
// By requiring at least 2 key/value slots, and rehashing at 75% |
634 |
|
// capacity, we guarantee that there will always be either an emptyslot |
635 |
|
// or a tombstone somewhere in the table. |
636 |
|
int h = 2 * Math.abs(System.identityHashCode(key) % table.length); |
637 |
|
int del = -1; |
638 |
|
int save = h; |
639 |
|
|
640 |
public void clear () |
do |
641 |
{ |
{ |
642 |
IdentityHashMap.this.clear (); |
if (table[h] == key) |
643 |
|
return h; |
644 |
|
if (table[h] == emptyslot) |
645 |
|
break; |
646 |
|
if (table[h] == tombstone && del < 0) |
647 |
|
del = h; |
648 |
|
h -= 2; |
649 |
|
if (h < 0) |
650 |
|
h = table.length - 2; |
651 |
} |
} |
652 |
}; |
while (h != save); |
653 |
|
|
654 |
|
return del < 0 ? h : del; |
655 |
} |
} |
656 |
|
|
657 |
private class IdentityIterator implements Iterator |
/** |
658 |
|
* This class allows parameterized iteration over IdentityHashMaps. Based |
659 |
|
* on its construction, it returns the key or value of a mapping, or |
660 |
|
* creates the appropriate Map.Entry object with the correct fail-fast |
661 |
|
* semantics and identity comparisons. |
662 |
|
* |
663 |
|
* @author Tom Tromey <tromey@redhat.com> |
664 |
|
* @author Eric Blake <ebb9@email.byu.edu> |
665 |
|
*/ |
666 |
|
private final class IdentityIterator implements Iterator |
667 |
{ |
{ |
668 |
static final int KEYS = 0; |
/** |
669 |
static final int VALUES = 1; |
* The type of this Iterator: {@link #KEYS}, {@link #VALUES}, |
670 |
static final int ENTRIES = 2; |
* or {@link #ENTRIES}. |
671 |
|
*/ |
672 |
// Type of iterator. |
final int type; |
673 |
int type; |
/** The number of modifications to the backing Map that we know about. */ |
674 |
// Location in the table. |
int knownMod = modCount; |
675 |
int loc; |
/** The number of elements remaining to be returned by next(). */ |
676 |
// How many items we've seen. |
int count = size; |
677 |
int seen; |
/** Location in the table. */ |
678 |
|
int loc = table.length; |
679 |
IdentityIterator (int type) |
|
680 |
|
/** |
681 |
|
* Construct a new Iterator with the supplied type. |
682 |
|
* @param type {@link #KEYS}, {@link #VALUES}, or {@link #ENTRIES} |
683 |
|
*/ |
684 |
|
IdentityIterator(int type) |
685 |
{ |
{ |
686 |
this.type = type; |
this.type = type; |
|
loc = 0; |
|
|
seen = 0; |
|
687 |
} |
} |
688 |
|
|
689 |
public boolean hasNext () |
/** |
690 |
|
* Returns true if the Iterator has more elements. |
691 |
|
* @return true if there are more elements |
692 |
|
* @throws ConcurrentModificationException if the Map was modified |
693 |
|
*/ |
694 |
|
public boolean hasNext() |
695 |
{ |
{ |
696 |
return seen < size; |
if (knownMod != modCount) |
697 |
|
throw new ConcurrentModificationException(); |
698 |
|
return count > 0; |
699 |
} |
} |
700 |
|
|
701 |
public Object next () |
/** |
702 |
|
* Returns the next element in the Iterator's sequential view. |
703 |
|
* @return the next element |
704 |
|
* @throws ConcurrentModificationException if the Map was modified |
705 |
|
* @throws NoSuchElementException if there is none |
706 |
|
*/ |
707 |
|
public Object next() |
708 |
{ |
{ |
709 |
while (true) |
if (knownMod != modCount) |
710 |
{ |
throw new ConcurrentModificationException(); |
711 |
loc += 2; |
if (count == 0) |
712 |
if (loc >= table.length) |
throw new NoSuchElementException(); |
713 |
throw new NoSuchElementException (); |
count--; |
714 |
if (table[loc] != tombstone && table[loc] != emptyslot) |
|
715 |
{ |
Object key; |
716 |
++seen; |
do |
717 |
return table[loc]; |
{ |
718 |
} |
loc -= 2; |
719 |
} |
key = table[loc]; |
720 |
|
} |
721 |
|
while (key == emptyslot || key == tombstone); |
722 |
|
|
723 |
|
return type == KEYS ? key : (type == VALUES ? table[loc + 1] |
724 |
|
: new IdentityEntry(loc)); |
725 |
} |
} |
726 |
|
|
727 |
public void remove () |
/** |
728 |
|
* Removes from the backing Map the last element which was fetched |
729 |
|
* with the <pre>next()</pre> method. |
730 |
|
* @throws ConcurrentModificationException if the Map was modified |
731 |
|
* @throws IllegalStateException if called when there is no last element |
732 |
|
*/ |
733 |
|
public void remove() |
734 |
{ |
{ |
735 |
if (loc >= table.length |
if (knownMod != modCount) |
736 |
|| table[loc] == tombstone |
throw new ConcurrentModificationException(); |
737 |
|| table[loc] == emptyslot) |
if (loc == table.length || table[loc] == tombstone) |
738 |
throw new IllegalStateException (); |
throw new IllegalStateException(); |
739 |
|
size--; |
740 |
|
modCount++; |
741 |
|
knownMod++; |
742 |
table[loc] = tombstone; |
table[loc] = tombstone; |
743 |
table[loc + 1] = tombstone; |
table[loc + 1] = tombstone; |
|
--size; |
|
744 |
} |
} |
745 |
} |
} // class IdentityIterator |
746 |
|
|
747 |
|
/** |
748 |
|
* This class provides Map.Entry objects for IdentityHashMaps. The entry |
749 |
|
* is fail-fast, and will throw a ConcurrentModificationException if |
750 |
|
* the underlying map is modified, or if remove is called on the iterator |
751 |
|
* that generated this object. It is identity based, so it violates |
752 |
|
* the general contract of Map.Entry, and is probably unsuitable for |
753 |
|
* comparison to normal maps; but it works among other IdentityHashMaps. |
754 |
|
* |
755 |
|
* @author Eric Blake <ebb9@email.byu.edu> |
756 |
|
*/ |
757 |
|
private final class IdentityEntry implements Map.Entry |
758 |
|
{ |
759 |
|
/** The location of this entry. */ |
760 |
|
final int loc; |
761 |
|
/** The number of modifications to the backing Map that we know about. */ |
762 |
|
final int knownMod = modCount; |
763 |
|
|
764 |
|
/** |
765 |
|
* Constructs the Entry. |
766 |
|
* |
767 |
|
* @param loc the location of this entry in table |
768 |
|
*/ |
769 |
|
IdentityEntry(int loc) |
770 |
|
{ |
771 |
|
this.loc = loc; |
772 |
|
} |
773 |
|
|
774 |
|
/** |
775 |
|
* Compares the specified object with this entry, using identity |
776 |
|
* semantics. Note that this can lead to undefined results with |
777 |
|
* Entry objects created by normal maps. |
778 |
|
* |
779 |
|
* @param o the object to compare |
780 |
|
* @return true if it is equal |
781 |
|
* @throws ConcurrentModificationException if the entry was invalidated |
782 |
|
* by modifying the Map or calling Iterator.remove() |
783 |
|
*/ |
784 |
|
public boolean equals(Object o) |
785 |
|
{ |
786 |
|
if (knownMod != modCount || table[loc] == tombstone) |
787 |
|
throw new ConcurrentModificationException(); |
788 |
|
if (! (o instanceof Map.Entry)) |
789 |
|
return false; |
790 |
|
Map.Entry e = (Map.Entry) o; |
791 |
|
return table[loc] == e.getKey() && table[loc + 1] == e.getValue(); |
792 |
|
} |
793 |
|
|
794 |
|
/** |
795 |
|
* Returns the key of this entry. |
796 |
|
* |
797 |
|
* @return the key |
798 |
|
* @throws ConcurrentModificationException if the entry was invalidated |
799 |
|
* by modifying the Map or calling Iterator.remove() |
800 |
|
*/ |
801 |
|
public Object getKey() |
802 |
|
{ |
803 |
|
if (knownMod != modCount || table[loc] == tombstone) |
804 |
|
throw new ConcurrentModificationException(); |
805 |
|
return table[loc]; |
806 |
|
} |
807 |
|
|
808 |
|
/** |
809 |
|
* Returns the value of this entry. |
810 |
|
* |
811 |
|
* @return the value |
812 |
|
* @throws ConcurrentModificationException if the entry was invalidated |
813 |
|
* by modifying the Map or calling Iterator.remove() |
814 |
|
*/ |
815 |
|
public Object getValue() |
816 |
|
{ |
817 |
|
if (knownMod != modCount || table[loc] == tombstone) |
818 |
|
throw new ConcurrentModificationException(); |
819 |
|
return table[loc + 1]; |
820 |
|
} |
821 |
|
|
822 |
private void readObject (ObjectInputStream s) |
/** |
823 |
|
* Returns the hashcode of the entry, using identity semantics. |
824 |
|
* Note that this can lead to undefined results with Entry objects |
825 |
|
* created by normal maps. |
826 |
|
* |
827 |
|
* @return the hash code |
828 |
|
* @throws ConcurrentModificationException if the entry was invalidated |
829 |
|
* by modifying the Map or calling Iterator.remove() |
830 |
|
*/ |
831 |
|
public int hashCode() |
832 |
|
{ |
833 |
|
if (knownMod != modCount || table[loc] == tombstone) |
834 |
|
throw new ConcurrentModificationException(); |
835 |
|
return (System.identityHashCode(table[loc]) |
836 |
|
^ System.identityHashCode(table[loc + 1])); |
837 |
|
} |
838 |
|
|
839 |
|
/** |
840 |
|
* Replaces the value of this mapping, and returns the old value. |
841 |
|
* |
842 |
|
* @param value the new value |
843 |
|
* @return the old value |
844 |
|
* @throws ConcurrentModificationException if the entry was invalidated |
845 |
|
* by modifying the Map or calling Iterator.remove() |
846 |
|
*/ |
847 |
|
public Object setValue(Object value) |
848 |
|
{ |
849 |
|
if (knownMod != modCount || table[loc] == tombstone) |
850 |
|
throw new ConcurrentModificationException(); |
851 |
|
Object r = table[loc + 1]; |
852 |
|
table[loc + 1] = value; |
853 |
|
return r; |
854 |
|
} |
855 |
|
|
856 |
|
/** |
857 |
|
* This provides a string representation of the entry. It is of the form |
858 |
|
* "key=value", where string concatenation is used on key and value. |
859 |
|
* |
860 |
|
* @return the string representation |
861 |
|
* @throws ConcurrentModificationException if the entry was invalidated |
862 |
|
* by modifying the Map or calling Iterator.remove() |
863 |
|
*/ |
864 |
|
public final String toString() |
865 |
|
{ |
866 |
|
if (knownMod != modCount || table[loc] == tombstone) |
867 |
|
throw new ConcurrentModificationException(); |
868 |
|
return table[loc] + "=" + table[loc + 1]; |
869 |
|
} |
870 |
|
} // class IdentityEntry |
871 |
|
|
872 |
|
/** |
873 |
|
* Reads the object from a serial stream. |
874 |
|
* |
875 |
|
* @param s the stream to read from |
876 |
|
* @throws ClassNotFoundException if the underlying stream fails |
877 |
|
* @throws IOException if the underlying stream fails |
878 |
|
* @serialData expects the size (int), followed by that many key (Object) |
879 |
|
* and value (Object) pairs, with the pairs in no particular |
880 |
|
* order |
881 |
|
*/ |
882 |
|
private void readObject(ObjectInputStream s) |
883 |
throws IOException, ClassNotFoundException |
throws IOException, ClassNotFoundException |
884 |
{ |
{ |
885 |
int num = s.readInt (); |
int num = s.readInt(); |
886 |
for (int i = 0; i < num; ++i) |
table = new Object[2 * Math.max(num * 2, DEFAULT_CAPACITY)]; |
887 |
{ |
// Read key/value pairs. |
888 |
Object key = s.readObject (); |
while (--num >= 0) |
889 |
Object value = s.readObject (); |
put(s.readObject(), s.readObject()); |
|
put (key, value); |
|
|
} |
|
890 |
} |
} |
891 |
|
|
892 |
private void writeObject (ObjectOutputStream s) |
/** |
893 |
|
* Writes the object to a serial stream. |
894 |
|
* |
895 |
|
* @param s the stream to write to |
896 |
|
* @throws IOException if the underlying stream fails |
897 |
|
* @serialData outputs the size (int), followed by that many key (Object) |
898 |
|
* and value (Object) pairs, with the pairs in no particular |
899 |
|
* order |
900 |
|
*/ |
901 |
|
private void writeObject(ObjectOutputStream s) |
902 |
throws IOException |
throws IOException |
903 |
{ |
{ |
904 |
s.writeInt (size); |
s.writeInt(size); |
905 |
Iterator it = entrySet ().iterator (); |
for (int i = table.length - 2; i >= 0; i -= 2) |
906 |
while (it.hasNext ()) |
{ |
907 |
{ |
Object key = table[i]; |
908 |
Map.Entry entry = (Map.Entry) it.next (); |
if (key != tombstone && key != emptyslot) |
909 |
s.writeObject (entry.getKey ()); |
{ |
910 |
s.writeObject (entry.getValue ()); |
s.writeObject(key); |
911 |
|
s.writeObject(table[i + 1]); |
912 |
|
} |
913 |
} |
} |
914 |
} |
} |
|
|
|
|
// Compute the hash value we will use for an object. |
|
|
private int getHash (Object o) |
|
|
{ |
|
|
return 2 * Math.abs (System.identityHashCode (o) % (table.length / 2)); |
|
|
} |
|
|
|
|
|
// Number of items in hash table. |
|
|
private int size; |
|
|
// The table itself. |
|
|
private Object[] table; |
|
|
|
|
|
// This object is used to mark deleted items. |
|
|
private static final Object tombstone = new Object (); |
|
|
// This object is used to mark empty slots. We need this because |
|
|
// using null is ambiguous. |
|
|
private static final Object emptyslot = new Object (); |
|
915 |
} |
} |