1 |
/* LinkedHashMap.java -- a class providing hashtable data structure, |
/* LinkedHashMap.java -- a class providing hashtable data structure, |
2 |
mapping Object --> Object, with linked list traversal |
mapping Object --> Object, with linked list traversal |
3 |
Copyright (C) 2001 Free Software Foundation, Inc. |
Copyright (C) 2001, 2002 Free Software Foundation, Inc. |
4 |
|
|
5 |
This file is part of GNU Classpath. |
This file is part of GNU Classpath. |
6 |
|
|
50 |
* can cause primary clustering) and rehashing (which does not fit very |
* can cause primary clustering) and rehashing (which does not fit very |
51 |
* well with Java's method of precomputing hash codes) are avoided. In |
* well with Java's method of precomputing hash codes) are avoided. In |
52 |
* addition, this maintains a doubly-linked list which tracks either |
* addition, this maintains a doubly-linked list which tracks either |
53 |
* insertion or access order. Note that the insertion order is not |
* insertion or access order. |
54 |
* modified if a <code>put</code> simply reinserts a key in the map. |
* <p> |
55 |
|
* |
56 |
|
* In insertion order, calling <code>put</code> adds the key to the end of |
57 |
|
* traversal, unless the key was already in the map; changing traversal order |
58 |
|
* requires removing and reinserting a key. On the other hand, in access |
59 |
|
* order, all calls to <code>put</code> and <code>get</code> cause the |
60 |
|
* accessed key to move to the end of the traversal list. Note that any |
61 |
|
* accesses to the map's contents via its collection views and iterators do |
62 |
|
* not affect the map's traversal order, since the collection views do not |
63 |
|
* call <code>put</code> or <code>get</code>. |
64 |
* <p> |
* <p> |
65 |
* |
* |
66 |
* One of the nice features of tracking insertion order is that you can |
* One of the nice features of tracking insertion order is that you can |
70 |
* <p> |
* <p> |
71 |
* |
* |
72 |
* When using this {@link #LinkedHashMap(int, float, boolean) constructor}, |
* When using this {@link #LinkedHashMap(int, float, boolean) constructor}, |
73 |
* you build an access-order mapping. This can be used to implement LRU |
* you can build an access-order mapping. This can be used to implement LRU |
74 |
* caches, for example. In this case, every invocation of <code>put</code>, |
* caches, for example. By overriding {@link #removeEldestEntry(Map.Entry)}, |
75 |
* <code>putAll</code>, or <code>get</code> moves the accessed entry to |
* you can also control the removal of the oldest entry, and thereby do |
76 |
* the end of the iteration list. By overriding |
* things like keep the map at a fixed size. |
|
* {@link #removeEldestEntry(Map.Entry)}, you can also control the |
|
|
* removal of the oldest entry, and thereby do things like keep the map |
|
|
* at a fixed size. |
|
77 |
* <p> |
* <p> |
78 |
* |
* |
79 |
* Under ideal circumstances (no collisions), LinkedHashMap offers O(1) |
* Under ideal circumstances (no collisions), LinkedHashMap offers O(1) |
80 |
* performance on most operations (<code>containsValue()</code> is, |
* performance on most operations (<code>containsValue()</code> is, |
81 |
* of course, O(n)). In the worst case (all keys map to the same |
* of course, O(n)). In the worst case (all keys map to the same |
82 |
* hash code -- very unlikely), most operations are O(n). |
* hash code -- very unlikely), most operations are O(n). Traversal is |
83 |
|
* faster than in HashMap (proportional to the map size, and not the space |
84 |
|
* allocated for the map), but other operations may be slower because of the |
85 |
|
* overhead of the maintaining the traversal order list. |
86 |
* <p> |
* <p> |
87 |
* |
* |
88 |
* LinkedHashMap accepts the null key and null values. It is not |
* LinkedHashMap accepts the null key and null values. It is not |
114 |
private static final long serialVersionUID = 3801124242820219131L; |
private static final long serialVersionUID = 3801124242820219131L; |
115 |
|
|
116 |
/** |
/** |
117 |
* The first Entry to iterate over. |
* The oldest Entry to begin iteration at. |
|
*/ |
|
|
transient LinkedHashEntry head; |
|
|
|
|
|
/** |
|
|
* The last Entry to iterate over. |
|
118 |
*/ |
*/ |
119 |
transient LinkedHashEntry tail; |
transient LinkedHashEntry root; |
120 |
|
|
121 |
/** |
/** |
122 |
* The iteration order of this linked hash map: <code>true</code> for |
* The iteration order of this linked hash map: <code>true</code> for |
123 |
* access-order, <code>false</code> for insertion-order. |
* access-order, <code>false</code> for insertion-order. |
124 |
* @serial |
* |
125 |
|
* @serial true for access order traversal |
126 |
*/ |
*/ |
127 |
final boolean accessOrder; |
final boolean accessOrder; |
128 |
|
|
132 |
*/ |
*/ |
133 |
class LinkedHashEntry extends HashEntry |
class LinkedHashEntry extends HashEntry |
134 |
{ |
{ |
135 |
/** The predecessor in the iteration list, null if this is the eldest. */ |
/** |
136 |
|
* The predecessor in the iteration list. If this entry is the root |
137 |
|
* (eldest), pred points to the newest entry. |
138 |
|
*/ |
139 |
LinkedHashEntry pred; |
LinkedHashEntry pred; |
140 |
|
|
141 |
/** The successor in the iteration list, null if this is the newest. */ |
/** The successor in the iteration list, null if this is the newest. */ |
142 |
LinkedHashEntry succ; |
LinkedHashEntry succ; |
143 |
|
|
144 |
/** |
/** |
145 |
* Simple constructor. |
* Simple constructor. |
146 |
|
* |
147 |
* @param key the key |
* @param key the key |
148 |
* @param value the value |
* @param value the value |
149 |
*/ |
*/ |
150 |
LinkedHashEntry(Object key, Object value) |
LinkedHashEntry(Object key, Object value) |
151 |
{ |
{ |
152 |
super(key, value); |
super(key, value); |
153 |
if (head == null) |
if (root == null) |
154 |
head = this; |
{ |
155 |
pred = tail; |
root = this; |
156 |
tail = this; |
pred = this; |
157 |
if (pred != null) |
} |
158 |
pred.succ = this; |
else |
159 |
|
{ |
160 |
|
pred = root.pred; |
161 |
|
pred.succ = this; |
162 |
|
root.pred = this; |
163 |
|
} |
164 |
} |
} |
165 |
|
|
166 |
/** |
/** |
167 |
* Sets the value of this entry, and shuffles it to the end of |
* Called when this entry is accessed via put or get. This version does |
168 |
* the list if this is in access-order. |
* the necessary bookkeeping to keep the doubly-linked list in order, |
169 |
* @param value the new value |
* after moving this element to the newest position in access order. |
|
* @return the prior value |
|
170 |
*/ |
*/ |
171 |
public Object setValue(Object value) |
void access() |
172 |
{ |
{ |
173 |
if (accessOrder && succ != null) |
if (accessOrder && succ != null) |
174 |
{ |
{ |
175 |
succ.pred = pred; |
modCount++; |
176 |
if (pred == null) |
if (this == root) |
177 |
head = succ; |
{ |
178 |
|
root = succ; |
179 |
|
pred.succ = this; |
180 |
|
succ = null; |
181 |
|
} |
182 |
else |
else |
183 |
pred.succ = succ; |
{ |
184 |
succ = null; |
pred.succ = succ; |
185 |
pred = tail; |
succ.pred = pred; |
186 |
pred.succ = this; |
succ = null; |
187 |
tail = this; |
pred = root.pred; |
188 |
|
pred.succ = this; |
189 |
|
} |
190 |
} |
} |
|
return super.setValue(value); |
|
191 |
} |
} |
192 |
|
|
193 |
/** |
/** |
194 |
* Called when this entry is removed from the map. This version does |
* Called when this entry is removed from the map. This version does |
195 |
* the necessary bookkeeping to keep the doubly-linked list in order. |
* the necessary bookkeeping to keep the doubly-linked list in order. |
196 |
|
* |
197 |
* @return the value of this key as it is removed |
* @return the value of this key as it is removed |
198 |
*/ |
*/ |
199 |
Object cleanup() |
Object cleanup() |
200 |
{ |
{ |
201 |
if (pred == null) |
if (this == root) |
202 |
head = succ; |
{ |
203 |
else |
root = succ; |
204 |
pred.succ = succ; |
if (succ != null) |
205 |
if (succ == null) |
succ.pred = pred; |
206 |
tail = pred; |
} |
207 |
|
else if (succ == null) |
208 |
|
{ |
209 |
|
pred.succ = null; |
210 |
|
root.pred = pred; |
211 |
|
} |
212 |
else |
else |
213 |
succ.pred = pred; |
{ |
214 |
|
pred.succ = succ; |
215 |
|
succ.pred = pred; |
216 |
|
} |
217 |
return value; |
return value; |
218 |
} |
} |
219 |
} |
} // class LinkedHashEntry |
220 |
|
|
221 |
/** |
/** |
222 |
* Construct a new insertion-ordered LinkedHashMap with the default |
* Construct a new insertion-ordered LinkedHashMap with the default |
280 |
* Construct a new LinkedHashMap with a specific inital capacity, load |
* Construct a new LinkedHashMap with a specific inital capacity, load |
281 |
* factor, and ordering mode. |
* factor, and ordering mode. |
282 |
* |
* |
283 |
* @param initialCapacity the initial capacity (>=0) |
* @param initialCapacity the initial capacity (>=0) |
284 |
* @param loadFactor the load factor (>0, not NaN) |
* @param loadFactor the load factor (>0, not NaN) |
285 |
* @param accessOrder true for access-order, false for insertion-order |
* @param accessOrder true for access-order, false for insertion-order |
|
* |
|
286 |
* @throws IllegalArgumentException if (initialCapacity < 0) || |
* @throws IllegalArgumentException if (initialCapacity < 0) || |
287 |
* ! (loadFactor > 0.0) |
* ! (loadFactor > 0.0) |
288 |
*/ |
*/ |
299 |
public void clear() |
public void clear() |
300 |
{ |
{ |
301 |
super.clear(); |
super.clear(); |
302 |
head = null; |
root = null; |
|
tail = null; |
|
303 |
} |
} |
304 |
|
|
305 |
/** |
/** |
307 |
* <code>o</code>, such that <code>o.equals(value)</code>. |
* <code>o</code>, such that <code>o.equals(value)</code>. |
308 |
* |
* |
309 |
* @param value the value to search for in this HashMap |
* @param value the value to search for in this HashMap |
|
* |
|
310 |
* @return <code>true</code> if at least one key maps to the value |
* @return <code>true</code> if at least one key maps to the value |
311 |
*/ |
*/ |
312 |
public boolean containsValue(Object value) |
public boolean containsValue(Object value) |
313 |
{ |
{ |
314 |
LinkedHashEntry e = head; |
LinkedHashEntry e = root; |
315 |
while (e != null) |
while (e != null) |
316 |
{ |
{ |
317 |
if (equals(value, e.value)) |
if (equals(value, e.value)) |
342 |
{ |
{ |
343 |
if (equals(key, e.key)) |
if (equals(key, e.key)) |
344 |
{ |
{ |
345 |
if (accessOrder) |
e.access(); |
|
{ |
|
|
modCount++; |
|
|
LinkedHashEntry l = (LinkedHashEntry) e; |
|
|
if (l.succ != null) |
|
|
{ |
|
|
l.succ.pred = l.pred; |
|
|
if (l.pred == null) |
|
|
head = l.succ; |
|
|
else |
|
|
l.pred.succ = l.succ; |
|
|
l.succ = null; |
|
|
l.pred = tail; |
|
|
tail.succ = l; |
|
|
tail = l; |
|
|
} |
|
|
} |
|
346 |
return e.value; |
return e.value; |
347 |
} |
} |
348 |
e = e.next; |
e = e.next; |
360 |
* <p> |
* <p> |
361 |
* |
* |
362 |
* For example, to keep the Map limited to 100 entries, override as follows: |
* For example, to keep the Map limited to 100 entries, override as follows: |
363 |
* |
* <pre> |
364 |
<pre>private static final int MAX_ENTRIES = 100; |
* private static final int MAX_ENTRIES = 100; |
365 |
|
* protected boolean removeEldestEntry(Map.Entry eldest) |
366 |
protected boolean removeEldestEntry(Map.Entry eldest) |
* { |
367 |
{ |
* return size() > MAX_ENTRIES; |
368 |
return size() > MAX_ENTRIES; |
* } |
369 |
} |
* </pre><p> |
|
</pre><p> |
|
370 |
* |
* |
371 |
* Typically, this method does not modify the map, but just uses the |
* Typically, this method does not modify the map, but just uses the |
372 |
* return value as an indication to <code>put</code> whether to proceed. |
* return value as an indication to <code>put</code> whether to proceed. |
373 |
* However, if you override it to modify the map, you must return false |
* However, if you override it to modify the map, you must return false |
374 |
* (indicating that <code>put</code> should do nothing), or face |
* (indicating that <code>put</code> should leave the modified map alone), |
375 |
* unspecified behavior. |
* or you face unspecified behavior. Remember that in access-order mode, |
376 |
|
* even calling <code>get</code> is a structural modification, but using |
377 |
|
* the collections views (such as <code>keySet</code>) is not. |
378 |
* <p> |
* <p> |
379 |
* |
* |
380 |
* This method is called after the eldest entry has been inserted, so |
* This method is called after the eldest entry has been inserted, so |
387 |
* returns true. For an access-order map, this is the least |
* returns true. For an access-order map, this is the least |
388 |
* recently accessed; for an insertion-order map, this is the |
* recently accessed; for an insertion-order map, this is the |
389 |
* earliest element inserted. |
* earliest element inserted. |
|
* |
|
390 |
* @return true if <code>eldest</code> should be removed |
* @return true if <code>eldest</code> should be removed |
391 |
*/ |
*/ |
392 |
protected boolean removeEldestEntry(Map.Entry eldest) |
protected boolean removeEldestEntry(Map.Entry eldest) |
404 |
* @param callRemove whether to call the removeEldestEntry method |
* @param callRemove whether to call the removeEldestEntry method |
405 |
* @see #put(Object, Object) |
* @see #put(Object, Object) |
406 |
* @see #removeEldestEntry(Map.Entry) |
* @see #removeEldestEntry(Map.Entry) |
407 |
|
* @see LinkedHashEntry#LinkedHashEntry(Object, Object) |
408 |
*/ |
*/ |
409 |
void addEntry(Object key, Object value, int idx, boolean callRemove) |
void addEntry(Object key, Object value, int idx, boolean callRemove) |
410 |
{ |
{ |
411 |
LinkedHashEntry e = new LinkedHashEntry(key, value); |
LinkedHashEntry e = new LinkedHashEntry(key, value); |
|
|
|
412 |
e.next = buckets[idx]; |
e.next = buckets[idx]; |
413 |
buckets[idx] = e; |
buckets[idx] = e; |
414 |
|
if (callRemove && removeEldestEntry(root)) |
415 |
if (callRemove && removeEldestEntry(head)) |
remove(root); |
|
remove(head); |
|
416 |
} |
} |
417 |
|
|
418 |
/** |
/** |
419 |
* Helper method, called by clone() to reset the doubly-linked list. |
* Helper method, called by clone() to reset the doubly-linked list. |
420 |
|
* |
421 |
* @param m the map to add entries from |
* @param m the map to add entries from |
422 |
* @see #clone() |
* @see #clone() |
423 |
*/ |
*/ |
424 |
void putAllInternal(Map m) |
void putAllInternal(Map m) |
425 |
{ |
{ |
426 |
head = null; |
root = null; |
|
tail = null; |
|
427 |
super.putAllInternal(m); |
super.putAllInternal(m); |
428 |
} |
} |
429 |
|
|
430 |
/** |
/** |
431 |
* Generates a parameterized iterator. This allows traversal to follow |
* Generates a parameterized iterator. This allows traversal to follow |
432 |
* the doubly-linked list instead of the random bin order of HashMap. |
* the doubly-linked list instead of the random bin order of HashMap. |
433 |
|
* |
434 |
* @param type {@link #KEYS}, {@link #VALUES}, or {@link #ENTRIES} |
* @param type {@link #KEYS}, {@link #VALUES}, or {@link #ENTRIES} |
435 |
* @return the appropriate iterator |
* @return the appropriate iterator |
436 |
*/ |
*/ |
438 |
{ |
{ |
439 |
return new Iterator() |
return new Iterator() |
440 |
{ |
{ |
441 |
/** The current Entry */ |
/** The current Entry. */ |
442 |
LinkedHashEntry current = head; |
LinkedHashEntry current = root; |
443 |
|
|
444 |
/** The previous Entry returned by next() */ |
/** The previous Entry returned by next(). */ |
445 |
LinkedHashEntry last; |
LinkedHashEntry last; |
446 |
|
|
447 |
/** The number of known modifications to the backing HashMap */ |
/** The number of known modifications to the backing Map. */ |
448 |
int knownMod = modCount; |
int knownMod = modCount; |
449 |
|
|
450 |
/** |
/** |
451 |
* Returns true if the Iterator has more elements. |
* Returns true if the Iterator has more elements. |
452 |
|
* |
453 |
* @return true if there are more elements |
* @return true if there are more elements |
454 |
* @throws ConcurrentModificationException if the HashMap was modified |
* @throws ConcurrentModificationException if the HashMap was modified |
455 |
*/ |
*/ |
462 |
|
|
463 |
/** |
/** |
464 |
* Returns the next element in the Iterator's sequential view. |
* Returns the next element in the Iterator's sequential view. |
465 |
|
* |
466 |
* @return the next element |
* @return the next element |
467 |
* @throws ConcurrentModificationException if the HashMap was modified |
* @throws ConcurrentModificationException if the HashMap was modified |
468 |
* @throws NoSuchElementException if there is none |
* @throws NoSuchElementException if there is none |
483 |
* with the <code>next()</code> method. |
* with the <code>next()</code> method. |
484 |
* |
* |
485 |
* @throws ConcurrentModificationException if the HashMap was modified |
* @throws ConcurrentModificationException if the HashMap was modified |
|
* |
|
486 |
* @throws IllegalStateException if called when there is no last element |
* @throws IllegalStateException if called when there is no last element |
487 |
*/ |
*/ |
488 |
public void remove() |
public void remove() |
491 |
throw new ConcurrentModificationException(); |
throw new ConcurrentModificationException(); |
492 |
if (last == null) |
if (last == null) |
493 |
throw new IllegalStateException(); |
throw new IllegalStateException(); |
|
|
|
494 |
LinkedHashMap.this.remove(last.key); |
LinkedHashMap.this.remove(last.key); |
495 |
last = null; |
last = null; |
496 |
knownMod++; |
knownMod++; |
497 |
} |
} |
498 |
}; |
}; |
499 |
} |
} |
500 |
} |
} // class LinkedHashMap |