1 |
/* java.util.WeakHashMap |
/* java.util.WeakHashMap -- a hashtable that keeps only weak references |
2 |
Copyright (C) 1999, 2000 Free Software Foundation, Inc. |
to its keys, allowing the virtual machine to reclaim them |
3 |
|
Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc. |
4 |
|
|
5 |
This file is part of GNU Classpath. |
This file is part of GNU Classpath. |
6 |
|
|
27 |
|
|
28 |
|
|
29 |
package java.util; |
package java.util; |
30 |
|
|
31 |
import java.lang.ref.WeakReference; |
import java.lang.ref.WeakReference; |
32 |
import java.lang.ref.ReferenceQueue; |
import java.lang.ref.ReferenceQueue; |
33 |
|
|
52 |
* has similar phenomenons: The size may spontaneously shrink, or an |
* has similar phenomenons: The size may spontaneously shrink, or an |
53 |
* entry, that was in the set before, suddenly disappears. <br> |
* entry, that was in the set before, suddenly disappears. <br> |
54 |
* |
* |
55 |
* A weak hash map is not meant for caches; use a normal map, with |
* A weak hash map is not meant for caches; use a normal map, with |
56 |
* soft references as values instead. <br> |
* soft references as values instead, or try {@link LinkedHashMap}. <br> |
57 |
* |
* |
58 |
* The weak hash map supports null values and null keys. Null keys |
* The weak hash map supports null values and null keys. The null key |
59 |
* are never deleted from the map (except explictly of course). |
* is never deleted from the map (except explictly of course). |
60 |
* The performance of the methods are similar to that of a hash map. <br> |
* The performance of the methods are similar to that of a hash map. <br> |
61 |
* |
* |
62 |
* The value object are strongly referenced by this table. So if a |
* The value objects are strongly referenced by this table. So if a |
63 |
* value object maintains a strong reference to the key (either direct |
* value object maintains a strong reference to the key (either direct |
64 |
* or indirect) the key will never be removed from this map. According |
* or indirect) the key will never be removed from this map. According |
65 |
* to Sun, this problem may be fixed in a future release. It is not |
* to Sun, this problem may be fixed in a future release. It is not |
66 |
* possible to do it with the jdk 1.2 reference model, though. |
* possible to do it with the jdk 1.2 reference model, though. |
67 |
* |
* |
68 |
* @since jdk1.2 |
* @author Jochen Hoenicke |
69 |
* @author Jochen Hoenicke |
* @author Eric Blake <ebb9@email.byu.edu> |
70 |
* @see HashMap |
* @see HashMap |
71 |
* @see WeakReference */ |
* @see WeakReference |
72 |
|
* @see LinkedHashMap |
73 |
|
* @since 1.2 |
74 |
|
* @status updated to 1.4 |
75 |
|
*/ |
76 |
public class WeakHashMap extends AbstractMap implements Map |
public class WeakHashMap extends AbstractMap implements Map |
77 |
{ |
{ |
78 |
/** |
/** |
79 |
* The default capacity for an instance of HashMap. |
* The default capacity for an instance of HashMap. |
80 |
* Sun's documentation mildly suggests that this (11) is the correct |
* Sun's documentation mildly suggests that this (11) is the correct |
81 |
* value. |
* value. |
82 |
*/ |
*/ |
83 |
private static final int DEFAULT_CAPACITY = 11; |
private static final int DEFAULT_CAPACITY = 11; |
84 |
|
|
85 |
/** |
/** |
86 |
* The default load factor of a HashMap |
* The default load factor of a HashMap. |
87 |
*/ |
*/ |
88 |
private static final float DEFAULT_LOAD_FACTOR = 0.75F; |
private static final float DEFAULT_LOAD_FACTOR = 0.75F; |
89 |
|
|
90 |
/** |
/** |
91 |
* This is used instead of the key value <i>null</i>. It is needed |
* This is used instead of the key value <i>null</i>. It is needed |
92 |
* to distinguish between an null key and a removed key. |
* to distinguish between an null key and a removed key. |
93 |
*/ |
*/ |
94 |
private static final Object NULL_KEY = new Object(); |
// Package visible for use by nested classes. |
95 |
|
static final Object NULL_KEY = new Object() |
96 |
|
{ |
97 |
|
/** |
98 |
|
* Sets the hashCode to 0, since that's what null would map to. |
99 |
|
* @return the hash code 0 |
100 |
|
*/ |
101 |
|
public int hashCode() |
102 |
|
{ |
103 |
|
return 0; |
104 |
|
} |
105 |
|
|
106 |
|
/** |
107 |
|
* Compares this key to the given object. Normally, an object should |
108 |
|
* NEVER compare equal to null, but since we don't publicize NULL_VALUE, |
109 |
|
* it saves bytecode to do so here. |
110 |
|
* @return true iff o is this or null |
111 |
|
*/ |
112 |
|
public boolean equals(Object o) |
113 |
|
{ |
114 |
|
return null == o || this == o; |
115 |
|
} |
116 |
|
}; |
117 |
|
|
118 |
/** |
/** |
119 |
* The reference queue where our buckets (which are WeakReferences) are |
* The reference queue where our buckets (which are WeakReferences) are |
120 |
* registered to. |
* registered to. |
121 |
*/ |
*/ |
122 |
private ReferenceQueue queue; |
private final ReferenceQueue queue; |
123 |
|
|
124 |
/** |
/** |
125 |
* The number of entries in this hash map. |
* The number of entries in this hash map. |
126 |
*/ |
*/ |
127 |
private int size; |
// Package visible for use by nested classes. |
128 |
|
int size; |
129 |
|
|
130 |
/** |
/** |
131 |
* The load factor of this WeakHashMap. This is the maximum ratio of |
* The load factor of this WeakHashMap. This is the maximum ratio of |
137 |
/** |
/** |
138 |
* The rounded product of the capacity (i.e. number of buckets) and |
* The rounded product of the capacity (i.e. number of buckets) and |
139 |
* the load factor. When the number of elements exceeds the |
* the load factor. When the number of elements exceeds the |
140 |
* threshold, the HashMap calls <pre>rehash()</pre>. |
* threshold, the HashMap calls <pre>rehash()</pre>. |
141 |
*/ |
*/ |
142 |
private int threshold; |
private int threshold; |
143 |
|
|
148 |
* by the garbage collection. Instead the iterators must make |
* by the garbage collection. Instead the iterators must make |
149 |
* sure to have strong references to the entries they rely on. |
* sure to have strong references to the entries they rely on. |
150 |
*/ |
*/ |
151 |
private int modCount; |
// Package visible for use by nested classes. |
152 |
|
int modCount; |
153 |
|
|
154 |
/** |
/** |
155 |
* The entry set. There is only one instance per hashmap, namely |
* The entry set. There is only one instance per hashmap, namely |
156 |
* theEntrySet. Note that the entry set may silently shrink, just |
* theEntrySet. Note that the entry set may silently shrink, just |
157 |
* like the WeakHashMap. |
* like the WeakHashMap. |
158 |
*/ |
*/ |
159 |
private class WeakEntrySet extends AbstractSet |
private final class WeakEntrySet extends AbstractSet |
160 |
{ |
{ |
161 |
/** |
/** |
162 |
* Returns the size of this set. |
* Returns the size of this set. |
163 |
|
* |
164 |
|
* @return the set size |
165 |
*/ |
*/ |
166 |
public int size() |
public int size() |
167 |
{ |
{ |
170 |
|
|
171 |
/** |
/** |
172 |
* Returns an iterator for all entries. |
* Returns an iterator for all entries. |
173 |
|
* |
174 |
|
* @return an Entry iterator |
175 |
*/ |
*/ |
176 |
public Iterator iterator() |
public Iterator iterator() |
177 |
{ |
{ |
178 |
return new Iterator() |
return new Iterator() |
179 |
{ |
{ |
180 |
/** |
/** |
181 |
* The entry that was returned by the last |
* The entry that was returned by the last |
182 |
* <code>next()</code> call. This is also the entry whose |
* <code>next()</code> call. This is also the entry whose |
183 |
* bucket should be removed by the <code>remove</code> call. <br> |
* bucket should be removed by the <code>remove</code> call. <br> |
184 |
* |
* |
185 |
* It is null, if the <code>next</code> method wasn't |
* It is null, if the <code>next</code> method wasn't |
186 |
* called yet, or if the entry was already removed. <br> |
* called yet, or if the entry was already removed. <br> |
187 |
* |
* |
188 |
* Remembering this entry here will also prevent it from |
* Remembering this entry here will also prevent it from |
189 |
* being removed under us, since the entry strongly refers |
* being removed under us, since the entry strongly refers |
190 |
* to the key. |
* to the key. |
191 |
*/ |
*/ |
192 |
WeakBucket.Entry lastEntry; |
WeakBucket.WeakEntry lastEntry; |
193 |
|
|
194 |
/** |
/** |
195 |
* The entry that will be returned by the next |
* The entry that will be returned by the next |
196 |
* <code>next()</code> call. It is <code>null</code> if there |
* <code>next()</code> call. It is <code>null</code> if there |
197 |
* is no further entry. <br> |
* is no further entry. <br> |
198 |
* |
* |
199 |
* Remembering this entry here will also prevent it from |
* Remembering this entry here will also prevent it from |
200 |
* being removed under us, since the entry strongly refers |
* being removed under us, since the entry strongly refers |
201 |
* to the key. |
* to the key. |
202 |
*/ |
*/ |
203 |
WeakBucket.Entry nextEntry = findNext(null); |
WeakBucket.WeakEntry nextEntry = findNext(null); |
204 |
|
|
205 |
/** |
/** |
206 |
* The known number of modification to the list, if it differs |
* The known number of modification to the list, if it differs |
207 |
* from the real number, we through an exception. |
* from the real number, we throw an exception. |
208 |
*/ |
*/ |
209 |
int knownMod = modCount; |
int knownMod = modCount; |
210 |
|
|
211 |
/** |
/** |
212 |
* Check the known number of modification to the number of |
* Check the known number of modification to the number of |
213 |
* modifications of the table. If it differs from the real |
* modifications of the table. If it differs from the real |
214 |
* number, we throw an exception. |
* number, we throw an exception. |
215 |
* @exception ConcurrentModificationException if the number |
* @throws ConcurrentModificationException if the number |
216 |
* of modifications doesn't match. |
* of modifications doesn't match. |
217 |
*/ |
*/ |
218 |
private void checkMod() |
private void checkMod() |
219 |
{ |
{ |
220 |
/* This method will get inlined */ |
// This method will get inlined. |
221 |
if (knownMod != modCount) |
cleanQueue(); |
222 |
throw new ConcurrentModificationException(); |
if (knownMod != modCount) |
223 |
} |
throw new ConcurrentModificationException(); |
224 |
|
} |
225 |
/** |
|
226 |
* Get a strong reference to the next entry after |
/** |
227 |
* lastBucket. |
* Get a strong reference to the next entry after |
228 |
* @param lastBucket the previous bucket, or null if we should |
* lastBucket. |
229 |
* get the first entry. |
* @param lastEntry the previous bucket, or null if we should |
230 |
* @return the next entry. |
* get the first entry. |
231 |
*/ |
* @return the next entry. |
232 |
private WeakBucket.Entry findNext(WeakBucket.Entry lastEntry) |
*/ |
233 |
{ |
private WeakBucket.WeakEntry findNext(WeakBucket.WeakEntry lastEntry) |
234 |
int slot; |
{ |
235 |
WeakBucket nextBucket; |
int slot; |
236 |
if (lastEntry != null) |
WeakBucket nextBucket; |
237 |
{ |
if (lastEntry != null) |
238 |
nextBucket = lastEntry.getBucket().next; |
{ |
239 |
slot = lastEntry.getBucket().slot; |
nextBucket = lastEntry.getBucket().next; |
240 |
} |
slot = lastEntry.getBucket().slot; |
241 |
else |
} |
242 |
{ |
else |
243 |
nextBucket = buckets[0]; |
{ |
244 |
slot = 0; |
nextBucket = buckets[0]; |
245 |
} |
slot = 0; |
246 |
|
} |
247 |
while (true) |
|
248 |
{ |
while (true) |
249 |
while (nextBucket != null) |
{ |
250 |
{ |
while (nextBucket != null) |
251 |
WeakBucket.Entry entry = nextBucket.getEntry(); |
{ |
252 |
if (entry != null) |
WeakBucket.WeakEntry entry = nextBucket.getEntry(); |
253 |
/* This is the next entry */ |
if (entry != null) |
254 |
return entry; |
// This is the next entry. |
255 |
|
return entry; |
256 |
/* entry was cleared, try next */ |
|
257 |
nextBucket = nextBucket.next; |
// Entry was cleared, try next. |
258 |
} |
nextBucket = nextBucket.next; |
259 |
|
} |
260 |
slot++; |
|
261 |
if (slot == buckets.length) |
slot++; |
262 |
/* No more buckets, we are through */ |
if (slot == buckets.length) |
263 |
return null; |
// No more buckets, we are through. |
264 |
|
return null; |
265 |
nextBucket = buckets[slot]; |
|
266 |
} |
nextBucket = buckets[slot]; |
267 |
} |
} |
268 |
|
} |
269 |
|
|
270 |
/** |
/** |
271 |
* Checks if there are more entries. |
* Checks if there are more entries. |
272 |
* @return true, iff there are more elements. |
* @return true, iff there are more elements. |
273 |
* @exception IllegalModificationException if the hash map was |
* @throws ConcurrentModificationException if the hash map was |
274 |
* modified. |
* modified. |
275 |
*/ |
*/ |
276 |
public boolean hasNext() |
public boolean hasNext() |
277 |
{ |
{ |
278 |
cleanQueue(); |
checkMod(); |
279 |
checkMod(); |
return nextEntry != null; |
280 |
return (nextEntry != null); |
} |
281 |
} |
|
282 |
|
/** |
283 |
/** |
* Returns the next entry. |
284 |
* Returns the next entry. |
* @return the next entry. |
285 |
* @return the next entry. |
* @throws ConcurrentModificationException if the hash map was |
286 |
* @exception IllegalModificationException if the hash map was |
* modified. |
287 |
* modified. |
* @throws NoSuchElementException if there is no entry. |
288 |
* @exception NoSuchElementException if there is no entry. |
*/ |
289 |
*/ |
public Object next() |
290 |
public Object next() |
{ |
291 |
{ |
checkMod(); |
292 |
cleanQueue(); |
if (nextEntry == null) |
293 |
checkMod(); |
throw new NoSuchElementException(); |
294 |
if (nextEntry == null) |
lastEntry = nextEntry; |
295 |
throw new NoSuchElementException(); |
nextEntry = findNext(lastEntry); |
296 |
lastEntry = nextEntry; |
return lastEntry; |
297 |
nextEntry = findNext(lastEntry); |
} |
298 |
return lastEntry; |
|
299 |
} |
/** |
300 |
|
* Removes the last returned entry from this set. This will |
301 |
/** |
* also remove the bucket of the underlying weak hash map. |
302 |
* Removes the last returned entry from this set. This will |
* @throws ConcurrentModificationException if the hash map was |
303 |
* also remove the bucket of the underlying weak hash map. |
* modified. |
304 |
* @exception IllegalModificationException if the hash map was |
* @throws IllegalStateException if <code>next()</code> was |
305 |
* modified. |
* never called or the element was already removed. |
306 |
* @exception IllegalStateException if <code>next()</code> was |
*/ |
307 |
* never called or the element was already removed. |
public void remove() |
308 |
*/ |
{ |
309 |
public void remove() |
checkMod(); |
310 |
{ |
if (lastEntry == null) |
311 |
cleanQueue(); |
throw new IllegalStateException(); |
312 |
checkMod(); |
modCount++; |
313 |
if (lastEntry == null) |
internalRemove(lastEntry.getBucket()); |
314 |
throw new IllegalStateException(); |
lastEntry = null; |
315 |
internalRemove(lastEntry.getBucket()); |
knownMod++; |
316 |
lastEntry = null; |
} |
|
modCount++; |
|
|
knownMod = modCount; |
|
|
} |
|
317 |
}; |
}; |
318 |
} |
} |
319 |
} |
} |
324 |
* number. <br> |
* number. <br> |
325 |
* |
* |
326 |
* It would be cleaner to have a WeakReference as field, instead of |
* It would be cleaner to have a WeakReference as field, instead of |
327 |
* extending it, but if a weak reference get cleared, we only get |
* extending it, but if a weak reference gets cleared, we only get |
328 |
* the weak reference (by queue.poll) and wouldn't know where to |
* the weak reference (by queue.poll) and wouldn't know where to |
329 |
* look for this reference in the hashtable, to remove that entry. |
* look for this reference in the hashtable, to remove that entry. |
330 |
* |
* |
331 |
* @author Jochen Hoenicke |
* @author Jochen Hoenicke |
332 |
*/ |
*/ |
333 |
private static class WeakBucket extends WeakReference |
private static class WeakBucket extends WeakReference |
334 |
{ |
{ |
335 |
/** |
/** |
336 |
* The value of this entry. The key is stored in the weak |
* The value of this entry. The key is stored in the weak |
337 |
* reference that we extend. |
* reference that we extend. |
338 |
*/ |
*/ |
339 |
Object value; |
Object value; |
340 |
|
|
341 |
/** |
/** |
342 |
* The next bucket describing another entry that uses the same |
* The next bucket describing another entry that uses the same |
343 |
* slot. |
* slot. |
344 |
*/ |
*/ |
345 |
WeakBucket next; |
WeakBucket next; |
346 |
|
|
347 |
/** |
/** |
348 |
* The slot of this entry. This should be |
* The slot of this entry. This should be |
349 |
* <pre> |
* <pre> |
350 |
* Math.abs(key.hashCode() % buckets.length) |
* Math.abs(key.hashCode() % buckets.length) |
351 |
* </pre> |
* </pre> |
360 |
* Creates a new bucket for the given key/value pair and the specified |
* Creates a new bucket for the given key/value pair and the specified |
361 |
* slot. |
* slot. |
362 |
* @param key the key |
* @param key the key |
363 |
|
* @param queue the queue the weak reference belongs to |
364 |
* @param value the value |
* @param value the value |
365 |
* @param slot the slot. This must match the slot where this bucket |
* @param slot the slot. This must match the slot where this bucket |
366 |
* will be enqueued. |
* will be enqueued. |
367 |
*/ |
*/ |
368 |
public WeakBucket(Object key, ReferenceQueue queue, Object value, |
public WeakBucket(Object key, ReferenceQueue queue, Object value, |
369 |
int slot) |
int slot) |
370 |
{ |
{ |
371 |
super(key, queue); |
super(key, queue); |
372 |
this.value = value; |
this.value = value; |
374 |
} |
} |
375 |
|
|
376 |
/** |
/** |
377 |
* This class gives the <code>Entry</code> representation of the |
* This class gives the <code>Entry</code> representation of the |
378 |
* current bucket. It also keeps a strong reference to the |
* current bucket. It also keeps a strong reference to the |
379 |
* key; bad things may happen otherwise. |
* key; bad things may happen otherwise. |
380 |
*/ |
*/ |
381 |
class Entry implements Map.Entry |
class WeakEntry implements Map.Entry |
382 |
{ |
{ |
383 |
/** |
/** |
384 |
* The strong ref to the key. |
* The strong ref to the key. |
387 |
|
|
388 |
/** |
/** |
389 |
* Creates a new entry for the key. |
* Creates a new entry for the key. |
390 |
|
* @param key the key |
391 |
*/ |
*/ |
392 |
public Entry(Object key) |
public WeakEntry(Object key) |
393 |
{ |
{ |
394 |
this.key = key; |
this.key = key; |
395 |
} |
} |
396 |
|
|
397 |
/** |
/** |
398 |
* Returns the underlying bucket. |
* Returns the underlying bucket. |
399 |
|
* @return the owning bucket |
400 |
*/ |
*/ |
401 |
public WeakBucket getBucket() |
public WeakBucket getBucket() |
402 |
{ |
{ |
403 |
return WeakBucket.this; |
return WeakBucket.this; |
404 |
} |
} |
405 |
|
|
406 |
/** |
/** |
407 |
* Returns the key. |
* Returns the key. |
408 |
|
* @return the key |
409 |
*/ |
*/ |
410 |
public Object getKey() |
public Object getKey() |
411 |
{ |
{ |
412 |
return key == NULL_KEY ? null : key; |
return key == NULL_KEY ? null : key; |
413 |
} |
} |
414 |
|
|
415 |
/** |
/** |
416 |
* Returns the value. |
* Returns the value. |
417 |
|
* @return the value |
418 |
*/ |
*/ |
419 |
public Object getValue() |
public Object getValue() |
420 |
{ |
{ |
421 |
return value; |
return value; |
422 |
} |
} |
423 |
|
|
424 |
/** |
/** |
425 |
* This changes the value. This change takes place in |
* This changes the value. This change takes place in |
426 |
* the underlying hash map. |
* the underlying hash map. |
427 |
|
* @param newVal the new value |
428 |
|
* @return the old value |
429 |
*/ |
*/ |
430 |
public Object setValue(Object newVal) |
public Object setValue(Object newVal) |
431 |
{ |
{ |
432 |
Object oldVal = value; |
Object oldVal = value; |
433 |
value = newVal; |
value = newVal; |
434 |
return oldVal; |
return oldVal; |
435 |
} |
} |
436 |
|
|
437 |
/** |
/** |
438 |
* The hashCode as specified in the Entry interface. |
* The hashCode as specified in the Entry interface. |
439 |
|
* @return the hash code |
440 |
*/ |
*/ |
441 |
public int hashCode() |
public int hashCode() |
442 |
{ |
{ |
443 |
return (key == NULL_KEY ? 0 : key.hashCode()) |
return key.hashCode() ^ WeakHashMap.hashCode(value); |
|
^ (value == null ? 0 : value.hashCode()); |
|
444 |
} |
} |
445 |
|
|
446 |
/** |
/** |
447 |
* The equals method as specified in the Entry interface. |
* The equals method as specified in the Entry interface. |
448 |
|
* @param o the object to compare to |
449 |
|
* @return true iff o represents the same key/value pair |
450 |
*/ |
*/ |
451 |
public boolean equals(Object o) |
public boolean equals(Object o) |
452 |
{ |
{ |
453 |
if (o instanceof Map.Entry) |
if (o instanceof Map.Entry) |
454 |
{ |
{ |
455 |
Map.Entry e = (Map.Entry) o; |
Map.Entry e = (Map.Entry) o; |
456 |
return (key == NULL_KEY |
return key.equals(e.getKey()) |
457 |
? e.getKey() == null : key.equals(e.getKey())) |
&& WeakHashMap.equals(value, e.getValue()); |
458 |
&& (value == null |
} |
459 |
? e.getValue() == null : value.equals(e.getValue())); |
return false; |
460 |
} |
} |
461 |
return false; |
|
462 |
|
public String toString() |
463 |
|
{ |
464 |
|
return key + "=" + value; |
465 |
} |
} |
466 |
} |
} |
467 |
|
|
468 |
/** |
/** |
469 |
* This returns the entry stored in this bucket, or null, if the |
* This returns the entry stored in this bucket, or null, if the |
470 |
* bucket got cleared in the mean time. |
* bucket got cleared in the mean time. |
471 |
|
* @return the Entry for this bucket, if it exists |
472 |
*/ |
*/ |
473 |
Entry getEntry() |
WeakEntry getEntry() |
474 |
{ |
{ |
475 |
final Object key = this.get(); |
final Object key = get(); |
476 |
if (key == null) |
if (key == null) |
477 |
return null; |
return null; |
478 |
return new Entry(key); |
return new WeakEntry(key); |
479 |
} |
} |
480 |
} |
} |
481 |
|
|
482 |
/** |
/** |
483 |
* The entry set returned by <code>entrySet()</code>. |
* The entry set returned by <code>entrySet()</code>. |
484 |
*/ |
*/ |
485 |
private WeakEntrySet theEntrySet; |
private final WeakEntrySet theEntrySet; |
486 |
|
|
487 |
/** |
/** |
488 |
* The hash buckets. This are linked lists. |
* The hash buckets. These are linked lists. |
489 |
*/ |
*/ |
490 |
private WeakBucket[] buckets; |
private WeakBucket[] buckets; |
491 |
|
|
501 |
/** |
/** |
502 |
* Creates a new weak hash map with default load factor and the given |
* Creates a new weak hash map with default load factor and the given |
503 |
* capacity. |
* capacity. |
504 |
* @param initialCapacity the initial capacity |
* @param initialCapacity the initial capacity |
505 |
|
* @throws IllegalArgumentException if initialCapacity is negative |
506 |
*/ |
*/ |
507 |
public WeakHashMap(int initialCapacity) |
public WeakHashMap(int initialCapacity) |
508 |
{ |
{ |
511 |
|
|
512 |
/** |
/** |
513 |
* Creates a new weak hash map with the given initial capacity and |
* Creates a new weak hash map with the given initial capacity and |
514 |
* load factor. |
* load factor. |
515 |
* @param initialCapacity the initial capacity. |
* @param initialCapacity the initial capacity. |
516 |
* @param loadFactor the load factor (see class description of HashMap). |
* @param loadFactor the load factor (see class description of HashMap). |
517 |
|
* @throws IllegalArgumentException if initialCapacity is negative, or |
518 |
|
* loadFactor is non-positive |
519 |
*/ |
*/ |
520 |
public WeakHashMap(int initialCapacity, float loadFactor) |
public WeakHashMap(int initialCapacity, float loadFactor) |
521 |
{ |
{ |
522 |
if (initialCapacity < 0 || loadFactor <= 0 || loadFactor > 1) |
// Check loadFactor for NaN as well. |
523 |
|
if (initialCapacity < 0 || ! (loadFactor > 0)) |
524 |
throw new IllegalArgumentException(); |
throw new IllegalArgumentException(); |
525 |
this.loadFactor = loadFactor; |
this.loadFactor = loadFactor; |
526 |
threshold = (int) (initialCapacity * loadFactor); |
threshold = (int) (initialCapacity * loadFactor); |
529 |
buckets = new WeakBucket[initialCapacity]; |
buckets = new WeakBucket[initialCapacity]; |
530 |
} |
} |
531 |
|
|
532 |
/** |
/** |
533 |
* simply hashes a non-null Object to its array index |
* Construct a new WeakHashMap with the same mappings as the given map. |
534 |
|
* The WeakHashMap has a default load factor of 0.75. |
535 |
|
* |
536 |
|
* @param m the map to copy |
537 |
|
* @throws NullPointerException if m is null |
538 |
|
* @since 1.3 |
539 |
|
*/ |
540 |
|
public WeakHashMap(Map m) |
541 |
|
{ |
542 |
|
this(m.size(), DEFAULT_LOAD_FACTOR); |
543 |
|
putAll(m); |
544 |
|
} |
545 |
|
|
546 |
|
/** |
547 |
|
* Simply hashes a non-null Object to its array index. |
548 |
|
* @param key the key to hash |
549 |
|
* @return its slot number |
550 |
*/ |
*/ |
551 |
private int hash(Object key) |
private int hash(Object key) |
552 |
{ |
{ |
562 |
* Currently the iterator maintains a strong reference to the key, so |
* Currently the iterator maintains a strong reference to the key, so |
563 |
* that is no problem. |
* that is no problem. |
564 |
*/ |
*/ |
565 |
private void cleanQueue() |
// Package visible for use by nested classes. |
566 |
|
void cleanQueue() |
567 |
{ |
{ |
568 |
Object bucket = queue.poll(); |
Object bucket = queue.poll(); |
569 |
while (bucket != null) |
while (bucket != null) |
570 |
{ |
{ |
571 |
internalRemove((WeakBucket) bucket); |
internalRemove((WeakBucket) bucket); |
572 |
bucket = queue.poll(); |
bucket = queue.poll(); |
573 |
} |
} |
574 |
} |
} |
575 |
|
|
576 |
/** |
/** |
577 |
* Rehashes this hashtable. This will be called by the |
* Rehashes this hashtable. This will be called by the |
578 |
* <code>add()</code> method if the size grows beyond the threshold. |
* <code>add()</code> method if the size grows beyond the threshold. |
579 |
* It will grow the bucket size at least by factor two and allocates |
* It will grow the bucket size at least by factor two and allocates |
580 |
* new buckets. |
* new buckets. |
581 |
*/ |
*/ |
582 |
private void rehash() |
private void rehash() |
583 |
{ |
{ |
584 |
WeakBucket[] oldBuckets = buckets; |
WeakBucket[] oldBuckets = buckets; |
585 |
int newsize = buckets.length * 2 + 1; // XXX should be prime. |
int newsize = buckets.length * 2 + 1; // XXX should be prime. |
586 |
threshold = (int) (newsize * loadFactor); |
threshold = (int) (newsize * loadFactor); |
587 |
buckets = new WeakBucket[newsize]; |
buckets = new WeakBucket[newsize]; |
588 |
|
|
589 |
/* Now we have to insert the buckets again. |
// Now we have to insert the buckets again. |
|
*/ |
|
590 |
for (int i = 0; i < oldBuckets.length; i++) |
for (int i = 0; i < oldBuckets.length; i++) |
591 |
{ |
{ |
592 |
WeakBucket bucket = oldBuckets[i]; |
WeakBucket bucket = oldBuckets[i]; |
593 |
WeakBucket nextBucket; |
WeakBucket nextBucket; |
594 |
while (bucket != null) |
while (bucket != null) |
595 |
{ |
{ |
596 |
nextBucket = bucket.next; |
nextBucket = bucket.next; |
597 |
|
|
598 |
Object key = bucket.get(); |
Object key = bucket.get(); |
599 |
if (key == null) |
if (key == null) |
600 |
{ |
{ |
601 |
/* This bucket should be removed; it is probably |
// This bucket should be removed; it is probably |
602 |
* already on the reference queue. We don't insert it |
// already on the reference queue. We don't insert it |
603 |
* at all, and mark it as cleared. */ |
// at all, and mark it as cleared. |
604 |
bucket.slot = -1; |
bucket.slot = -1; |
605 |
size--; |
size--; |
606 |
} |
} |
607 |
else |
else |
608 |
{ |
{ |
609 |
/* add this bucket to its new slot */ |
// Add this bucket to its new slot. |
610 |
int slot = hash(key); |
int slot = hash(key); |
611 |
bucket.slot = slot; |
bucket.slot = slot; |
612 |
bucket.next = buckets[slot]; |
bucket.next = buckets[slot]; |
613 |
buckets[slot] = bucket; |
buckets[slot] = bucket; |
614 |
} |
} |
615 |
bucket = nextBucket; |
bucket = nextBucket; |
616 |
} |
} |
617 |
} |
} |
618 |
} |
} |
619 |
|
|
620 |
/** |
/** |
621 |
* Finds the entry corresponding to key. Since it returns an Entry |
* Finds the entry corresponding to key. Since it returns an Entry |
622 |
* it will also prevent the key from being removed under us. |
* it will also prevent the key from being removed under us. |
623 |
* @param key the key. It may be null. |
* @param key the key, may be null |
624 |
* @return The WeakBucket.Entry or null, if the key wasn't found. |
* @return The WeakBucket.WeakEntry or null, if the key wasn't found. |
625 |
*/ |
*/ |
626 |
private WeakBucket.Entry internalGet(Object key) |
private WeakBucket.WeakEntry internalGet(Object key) |
627 |
{ |
{ |
628 |
if (key == null) |
if (key == null) |
629 |
key = NULL_KEY; |
key = NULL_KEY; |
631 |
WeakBucket bucket = buckets[slot]; |
WeakBucket bucket = buckets[slot]; |
632 |
while (bucket != null) |
while (bucket != null) |
633 |
{ |
{ |
634 |
WeakBucket.Entry entry = bucket.getEntry(); |
WeakBucket.WeakEntry entry = bucket.getEntry(); |
635 |
if (entry != null && key.equals(entry.key)) |
if (entry != null && key.equals(entry.key)) |
636 |
return entry; |
return entry; |
637 |
|
|
638 |
bucket = bucket.next; |
bucket = bucket.next; |
639 |
} |
} |
640 |
return null; |
return null; |
641 |
} |
} |
659 |
/** |
/** |
660 |
* Removes a bucket from this hash map, if it wasn't removed before |
* Removes a bucket from this hash map, if it wasn't removed before |
661 |
* (e.g. one time through rehashing and one time through reference queue) |
* (e.g. one time through rehashing and one time through reference queue) |
662 |
* @param bucket the bucket to remove. |
* @param bucket the bucket to remove. |
663 |
*/ |
*/ |
664 |
private void internalRemove(WeakBucket bucket) |
private void internalRemove(WeakBucket bucket) |
665 |
{ |
{ |
666 |
int slot = bucket.slot; |
int slot = bucket.slot; |
667 |
if (slot == -1) |
if (slot == -1) |
668 |
/* this bucket was already removed. */ |
// This bucket was already removed. |
669 |
return; |
return; |
670 |
|
|
671 |
/* mark the bucket as removed. This is necessary, since the |
// Mark the bucket as removed. This is necessary, since the |
672 |
* bucket may be enqueued later by the garbage collection and |
// bucket may be enqueued later by the garbage collection, and |
673 |
* internalRemove, will be called a second time. |
// internalRemove will be called a second time. |
|
*/ |
|
674 |
bucket.slot = -1; |
bucket.slot = -1; |
675 |
if (buckets[slot] == bucket) |
if (buckets[slot] == bucket) |
676 |
buckets[slot] = bucket.next; |
buckets[slot] = bucket.next; |
677 |
else |
else |
678 |
{ |
{ |
679 |
WeakBucket prev = buckets[slot]; |
WeakBucket prev = buckets[slot]; |
680 |
/* This may throw a NullPointerException. It shouldn't but if |
/* This may throw a NullPointerException. It shouldn't but if |
681 |
* a race condition occured (two threads removing the same |
* a race condition occured (two threads removing the same |
682 |
* bucket at the same time) it may happen. <br> |
* bucket at the same time) it may happen. <br> |
683 |
* But with race condition many much worse things may happen |
* But with race condition many much worse things may happen |
684 |
* anyway. |
* anyway. |
685 |
*/ |
*/ |
686 |
while (prev.next != bucket) |
while (prev.next != bucket) |
687 |
prev = prev.next; |
prev = prev.next; |
688 |
prev.next = bucket.next; |
prev.next = bucket.next; |
689 |
} |
} |
690 |
size--; |
size--; |
691 |
} |
} |
692 |
|
|
693 |
/** |
/** |
694 |
* Returns the size of this hash map. Note that the size() may shrink |
* Returns the size of this hash map. Note that the size() may shrink |
695 |
* spontanously, if the some of the keys were only weakly reachable. |
* spontaneously, if the some of the keys were only weakly reachable. |
696 |
* @return the number of entries in this hash map. |
* @return the number of entries in this hash map. |
697 |
*/ |
*/ |
698 |
public int size() |
public int size() |
714 |
|
|
715 |
/** |
/** |
716 |
* Tells if the map contains the given key. Note that the result |
* Tells if the map contains the given key. Note that the result |
717 |
* may change spontanously, if all the key was only weakly |
* may change spontanously, if the key was only weakly |
718 |
* reachable. |
* reachable. |
719 |
* @return true, iff the map contains an entry for the given key. |
* @param key the key to look for |
720 |
|
* @return true, iff the map contains an entry for the given key. |
721 |
*/ |
*/ |
722 |
public boolean containsKey(Object key) |
public boolean containsKey(Object key) |
723 |
{ |
{ |
726 |
} |
} |
727 |
|
|
728 |
/** |
/** |
729 |
* Gets the value the key will be mapped to. |
* Gets the value the key is mapped to. |
730 |
* @return the value the key was mapped to. It returns null if |
* @return the value the key was mapped to. It returns null if |
731 |
* the key wasn't in this map, or if the mapped value was explicitly |
* the key wasn't in this map, or if the mapped value was |
732 |
* set to null. |
* explicitly set to null. |
733 |
*/ |
*/ |
734 |
public Object get(Object key) |
public Object get(Object key) |
735 |
{ |
{ |
736 |
cleanQueue(); |
cleanQueue(); |
737 |
WeakBucket.Entry entry = internalGet(key); |
WeakBucket.WeakEntry entry = internalGet(key); |
738 |
return entry == null ? null : entry.getValue(); |
return entry == null ? null : entry.getValue(); |
739 |
} |
} |
740 |
|
|
741 |
/** |
/** |
742 |
* Adds a new key/value mapping to this map. |
* Adds a new key/value mapping to this map. |
743 |
* @param key the key. This may be null. |
* @param key the key, may be null |
744 |
* @param value the value. This may be null. |
* @param value the value, may be null |
745 |
* @return the value the key was mapped to previously. It returns |
* @return the value the key was mapped to previously. It returns |
746 |
* null if the key wasn't in this map, or if the mapped value was |
* null if the key wasn't in this map, or if the mapped value |
747 |
* explicitly set to null. |
* was explicitly set to null. |
748 |
*/ |
*/ |
749 |
public Object put(Object key, Object value) |
public Object put(Object key, Object value) |
750 |
{ |
{ |
751 |
cleanQueue(); |
cleanQueue(); |
752 |
WeakBucket.Entry entry = internalGet(key); |
WeakBucket.WeakEntry entry = internalGet(key); |
753 |
if (entry != null) |
if (entry != null) |
754 |
return entry.setValue(value); |
return entry.setValue(value); |
755 |
|
|
756 |
|
modCount++; |
757 |
if (size >= threshold) |
if (size >= threshold) |
758 |
rehash(); |
rehash(); |
759 |
|
|
760 |
internalAdd(key, value); |
internalAdd(key, value); |
|
modCount++; |
|
761 |
return null; |
return null; |
762 |
} |
} |
763 |
|
|
765 |
* Removes the key and the corresponding value from this map. |
* Removes the key and the corresponding value from this map. |
766 |
* @param key the key. This may be null. |
* @param key the key. This may be null. |
767 |
* @return the value the key was mapped to previously. It returns |
* @return the value the key was mapped to previously. It returns |
768 |
* null if the key wasn't in this map, or if the mapped value was |
* null if the key wasn't in this map, or if the mapped value was |
769 |
* explicitly set to null. */ |
* explicitly set to null. |
770 |
|
*/ |
771 |
public Object remove(Object key) |
public Object remove(Object key) |
772 |
{ |
{ |
773 |
cleanQueue(); |
cleanQueue(); |
774 |
WeakBucket.Entry entry = internalGet(key); |
WeakBucket.WeakEntry entry = internalGet(key); |
775 |
if (entry == null) |
if (entry == null) |
776 |
{ |
return null; |
777 |
return null; |
|
|
} |
|
|
internalRemove(entry.getBucket()); |
|
778 |
modCount++; |
modCount++; |
779 |
|
internalRemove(entry.getBucket()); |
780 |
return entry.getValue(); |
return entry.getValue(); |
781 |
} |
} |
782 |
|
|
785 |
* set will not have strong references to the keys, so they can be |
* set will not have strong references to the keys, so they can be |
786 |
* silently removed. The returned set has therefore the same |
* silently removed. The returned set has therefore the same |
787 |
* strange behaviour (shrinking size(), disappearing entries) as |
* strange behaviour (shrinking size(), disappearing entries) as |
788 |
* this weak hash map. |
* this weak hash map. |
789 |
* @return a set representation of the entries. */ |
* @return a set representation of the entries. |
790 |
|
*/ |
791 |
public Set entrySet() |
public Set entrySet() |
792 |
{ |
{ |
793 |
cleanQueue(); |
cleanQueue(); |
794 |
return theEntrySet; |
return theEntrySet; |
795 |
} |
} |
796 |
|
|
797 |
|
/** |
798 |
|
* Clears all entries from this map. |
799 |
|
*/ |
800 |
|
public void clear() |
801 |
|
{ |
802 |
|
super.clear(); |
803 |
|
} |
804 |
|
|
805 |
|
/** |
806 |
|
* Returns true if the map contains at least one key which points to |
807 |
|
* the specified object as a value. Note that the result |
808 |
|
* may change spontanously, if its key was only weakly reachable. |
809 |
|
* @param value the value to search for |
810 |
|
* @return true if it is found in the set. |
811 |
|
*/ |
812 |
|
public boolean containsValue(Object value) |
813 |
|
{ |
814 |
|
cleanQueue(); |
815 |
|
return super.containsValue(value); |
816 |
|
} |
817 |
|
|
818 |
|
/** |
819 |
|
* Returns a set representation of the keys in this map. This |
820 |
|
* set will not have strong references to the keys, so they can be |
821 |
|
* silently removed. The returned set has therefore the same |
822 |
|
* strange behaviour (shrinking size(), disappearing entries) as |
823 |
|
* this weak hash map. |
824 |
|
* @return a set representation of the keys. |
825 |
|
*/ |
826 |
|
public Set keySet() |
827 |
|
{ |
828 |
|
cleanQueue(); |
829 |
|
return super.keySet(); |
830 |
|
} |
831 |
|
|
832 |
|
/** |
833 |
|
* Puts all of the mappings from the given map into this one. If the |
834 |
|
* key already exists in this map, its value is replaced. |
835 |
|
* @param m the map to copy in |
836 |
|
*/ |
837 |
|
public void putAll(Map m) |
838 |
|
{ |
839 |
|
super.putAll(m); |
840 |
|
} |
841 |
|
|
842 |
|
/** |
843 |
|
* Returns a collection representation of the values in this map. This |
844 |
|
* collection will not have strong references to the keys, so mappings |
845 |
|
* can be silently removed. The returned collection has therefore the same |
846 |
|
* strange behaviour (shrinking size(), disappearing entries) as |
847 |
|
* this weak hash map. |
848 |
|
* @return a collection representation of the values. |
849 |
|
*/ |
850 |
|
public Collection values() |
851 |
|
{ |
852 |
|
cleanQueue(); |
853 |
|
return super.values(); |
854 |
|
} |
855 |
} |
} |