1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.collections.map;
18
19 import java.util.AbstractCollection;
20 import java.util.AbstractSet;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.Iterator;
24 import java.util.Map;
25 import java.util.NoSuchElementException;
26 import java.util.Set;
27
28 import org.apache.commons.collections.KeyValue;
29
30 /**
31 * A StaticBucketMap is an efficient, thread-safe implementation of
32 * <code>java.util.Map</code> that performs well in in a highly
33 * thread-contentious environment. The map supports very efficient
34 * {@link #get(Object) get}, {@link #put(Object,Object) put},
35 * {@link #remove(Object) remove} and {@link #containsKey(Object) containsKey}
36 * operations, assuming (approximate) uniform hashing and
37 * that the number of entries does not exceed the number of buckets. If the
38 * number of entries exceeds the number of buckets or if the hash codes of the
39 * objects are not uniformly distributed, these operations have a worst case
40 * scenario that is proportional to the number of elements in the map
41 * (<i>O(n)</i>).<p>
42 *
43 * Each bucket in the hash table has its own monitor, so two threads can
44 * safely operate on the map at the same time, often without incurring any
45 * monitor contention. This means that you don't have to wrap instances
46 * of this class with {@link java.util.Collections#synchronizedMap(Map)};
47 * instances are already thread-safe. Unfortunately, however, this means
48 * that this map implementation behaves in ways you may find disconcerting.
49 * Bulk operations, such as {@link #putAll(Map) putAll} or the
50 * {@link Collection#retainAll(Collection) retainAll} operation in collection
51 * views, are <i>not</i> atomic. If two threads are simultaneously
52 * executing
53 *
54 * <pre>
55 * staticBucketMapInstance.putAll(map);
56 * </pre>
57 *
58 * and
59 *
60 * <pre>
61 * staticBucketMapInstance.entrySet().removeAll(map.entrySet());
62 * </pre>
63 *
64 * then the results are generally random. Those two statement could cancel
65 * each other out, leaving <code>staticBucketMapInstance</code> essentially
66 * unchanged, or they could leave some random subset of <code>map</code> in
67 * <code>staticBucketMapInstance</code>.<p>
68 *
69 * Also, much like an encyclopedia, the results of {@link #size()} and
70 * {@link #isEmpty()} are out-of-date as soon as they are produced.<p>
71 *
72 * The iterators returned by the collection views of this class are <i>not</i>
73 * fail-fast. They will <i>never</i> raise a
74 * {@link java.util.ConcurrentModificationException}. Keys and values
75 * added to the map after the iterator is created do not necessarily appear
76 * during iteration. Similarly, the iterator does not necessarily fail to
77 * return keys and values that were removed after the iterator was created.<p>
78 *
79 * Finally, unlike {@link java.util.HashMap}-style implementations, this
80 * class <i>never</i> rehashes the map. The number of buckets is fixed
81 * at construction time and never altered. Performance may degrade if
82 * you do not allocate enough buckets upfront.<p>
83 *
84 * The {@link #atomic(Runnable)} method is provided to allow atomic iterations
85 * and bulk operations; however, overuse of {@link #atomic(Runnable) atomic}
86 * will basically result in a map that's slower than an ordinary synchronized
87 * {@link java.util.HashMap}.
88 *
89 * Use this class if you do not require reliable bulk operations and
90 * iterations, or if you can make your own guarantees about how bulk
91 * operations will affect the map.<p>
92 *
93 * @since 3.0 (previously in main package v2.1)
94 * @version $Id: StaticBucketMap.java 1429905 2013-01-07 17:15:14Z ggregory $
95 */
96 public final class StaticBucketMap<K, V> extends AbstractIterableMap<K, V> {
97
98 /** The default number of buckets to use */
99 private static final int DEFAULT_BUCKETS = 255;
100 /** The array of buckets, where the actual data is held */
101 private final Node<K, V>[] buckets;
102 /** The matching array of locks */
103 private final Lock[] locks;
104
105 /**
106 * Initializes the map with the default number of buckets (255).
107 */
108 public StaticBucketMap() {
109 this(DEFAULT_BUCKETS);
110 }
111
112 /**
113 * Initializes the map with a specified number of buckets. The number
114 * of buckets is never below 17, and is always an odd number (StaticBucketMap
115 * ensures this). The number of buckets is inversely proportional to the
116 * chances for thread contention. The fewer buckets, the more chances for
117 * thread contention. The more buckets the fewer chances for thread
118 * contention.
119 *
120 * @param numBuckets the number of buckets for this map
121 */
122 @SuppressWarnings("unchecked")
123 public StaticBucketMap(final int numBuckets) {
124 int size = Math.max(17, numBuckets);
125
126 // Ensure that bucketSize is never a power of 2 (to ensure maximal distribution)
127 if (size % 2 == 0) {
128 size--;
129 }
130
131 buckets = new Node[size];
132 locks = new Lock[size];
133
134 for (int i = 0; i < size; i++) {
135 locks[i] = new Lock();
136 }
137 }
138
139 //-----------------------------------------------------------------------
140 /**
141 * Determine the exact hash entry for the key. The hash algorithm
142 * is rather simplistic, but it does the job:
143 *
144 * <pre>
145 * He = |Hk mod n|
146 * </pre>
147 *
148 * <p>
149 * He is the entry's hashCode, Hk is the key's hashCode, and n is
150 * the number of buckets.
151 * </p>
152 */
153 private final int getHash(final Object key) {
154 if (key == null) {
155 return 0;
156 }
157 int hash = key.hashCode();
158 hash += ~(hash << 15);
159 hash ^= (hash >>> 10);
160 hash += (hash << 3);
161 hash ^= (hash >>> 6);
162 hash += ~(hash << 11);
163 hash ^= (hash >>> 16);
164 hash %= buckets.length;
165 return (hash < 0) ? hash * -1 : hash;
166 }
167
168 /**
169 * Gets the current size of the map.
170 * The value is computed fresh each time the method is called.
171 *
172 * @return the current size
173 */
174 public int size() {
175 int cnt = 0;
176
177 for (int i = 0; i < buckets.length; i++) {
178 synchronized(locks[i]) {
179 cnt += locks[i].size;
180 }
181 }
182 return cnt;
183 }
184
185 /**
186 * Checks if the size is currently zero.
187 *
188 * @return true if empty
189 */
190 public boolean isEmpty() {
191 return (size() == 0);
192 }
193
194 /**
195 * Gets the value associated with the key.
196 *
197 * @param key the key to retrieve
198 * @return the associated value
199 */
200 public V get(final Object key) {
201 final int hash = getHash(key);
202
203 synchronized (locks[hash]) {
204 Node<K, V> n = buckets[hash];
205
206 while (n != null) {
207 if (n.key == key || (n.key != null && n.key.equals(key))) {
208 return n.value;
209 }
210
211 n = n.next;
212 }
213 }
214 return null;
215 }
216
217 /**
218 * Checks if the map contains the specified key.
219 *
220 * @param key the key to check
221 * @return true if found
222 */
223 public boolean containsKey(final Object key) {
224 final int hash = getHash(key);
225
226 synchronized (locks[hash]) {
227 Node<K, V> n = buckets[hash];
228
229 while (n != null) {
230 if (n.key == key || (n.key != null && n.key.equals(key))) {
231 return true;
232 }
233
234 n = n.next;
235 }
236 }
237 return false;
238 }
239
240 /**
241 * Checks if the map contains the specified value.
242 *
243 * @param value the value to check
244 * @return true if found
245 */
246 public boolean containsValue(final Object value) {
247 for (int i = 0; i < buckets.length; i++) {
248 synchronized (locks[i]) {
249 Node<K, V> n = buckets[i];
250
251 while (n != null) {
252 if (n.value == value || (n.value != null && n.value.equals(value))) {
253 return true;
254 }
255
256 n = n.next;
257 }
258 }
259 }
260 return false;
261 }
262
263 //-----------------------------------------------------------------------
264 /**
265 * Puts a new key value mapping into the map.
266 *
267 * @param key the key to use
268 * @param value the value to use
269 * @return the previous mapping for the key
270 */
271 public V put(final K key, final V value) {
272 final int hash = getHash(key);
273
274 synchronized (locks[hash]) {
275 Node<K, V> n = buckets[hash];
276
277 if (n == null) {
278 n = new Node<K, V>();
279 n.key = key;
280 n.value = value;
281 buckets[hash] = n;
282 locks[hash].size++;
283 return null;
284 }
285
286 // Set n to the last node in the linked list. Check each key along the way
287 // If the key is found, then change the value of that node and return
288 // the old value.
289 for (Node<K, V> next = n; next != null; next = next.next) {
290 n = next;
291
292 if (n.key == key || (n.key != null && n.key.equals(key))) {
293 final V returnVal = n.value;
294 n.value = value;
295 return returnVal;
296 }
297 }
298
299 // The key was not found in the current list of nodes, add it to the end
300 // in a new node.
301 final Node<K, V> newNode = new Node<K, V>();
302 newNode.key = key;
303 newNode.value = value;
304 n.next = newNode;
305 locks[hash].size++;
306 }
307 return null;
308 }
309
310 /**
311 * Removes the specified key from the map.
312 *
313 * @param key the key to remove
314 * @return the previous value at this key
315 */
316 public V remove(final Object key) {
317 final int hash = getHash(key);
318
319 synchronized (locks[hash]) {
320 Node<K, V> n = buckets[hash];
321 Node<K, V> prev = null;
322
323 while (n != null) {
324 if (n.key == key || (n.key != null && n.key.equals(key))) {
325 // Remove this node from the linked list of nodes.
326 if (null == prev) {
327 // This node was the head, set the next node to be the new head.
328 buckets[hash] = n.next;
329 } else {
330 // Set the next node of the previous node to be the node after this one.
331 prev.next = n.next;
332 }
333 locks[hash].size--;
334 return n.value;
335 }
336
337 prev = n;
338 n = n.next;
339 }
340 }
341 return null;
342 }
343
344 //-----------------------------------------------------------------------
345 /**
346 * Gets the key set.
347 *
348 * @return the key set
349 */
350 public Set<K> keySet() {
351 return new KeySet();
352 }
353
354 /**
355 * Gets the values.
356 *
357 * @return the values
358 */
359 public Collection<V> values() {
360 return new Values();
361 }
362
363 /**
364 * Gets the entry set.
365 *
366 * @return the entry set
367 */
368 public Set<Map.Entry<K, V>> entrySet() {
369 return new EntrySet();
370 }
371
372 //-----------------------------------------------------------------------
373 /**
374 * Puts all the entries from the specified map into this map.
375 * This operation is <b>not atomic</b> and may have undesired effects.
376 *
377 * @param map the map of entries to add
378 */
379 public void putAll(final Map<? extends K, ? extends V> map) {
380 for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
381 put(entry.getKey(), entry.getValue());
382 }
383 }
384
385 /**
386 * Clears the map of all entries.
387 */
388 public void clear() {
389 for (int i = 0; i < buckets.length; i++) {
390 final Lock lock = locks[i];
391 synchronized (lock) {
392 buckets[i] = null;
393 lock.size = 0;
394 }
395 }
396 }
397
398 /**
399 * Compares this map to another, as per the Map specification.
400 *
401 * @param obj the object to compare to
402 * @return true if equal
403 */
404 @Override
405 public boolean equals(final Object obj) {
406 if (obj == this) {
407 return true;
408 }
409 if (obj instanceof Map<?, ?> == false) {
410 return false;
411 }
412 final Map<?, ?> other = (Map<?, ?>) obj;
413 return entrySet().equals(other.entrySet());
414 }
415
416 /**
417 * Gets the hash code, as per the Map specification.
418 *
419 * @return the hash code
420 */
421 @Override
422 public int hashCode() {
423 int hashCode = 0;
424
425 for (int i = 0; i < buckets.length; i++) {
426 synchronized (locks[i]) {
427 Node<K, V> n = buckets[i];
428
429 while (n != null) {
430 hashCode += n.hashCode();
431 n = n.next;
432 }
433 }
434 }
435 return hashCode;
436 }
437
438 //-----------------------------------------------------------------------
439 /**
440 * The Map.Entry for the StaticBucketMap.
441 */
442 private static final class Node<K, V> implements Map.Entry<K, V>, KeyValue<K, V> {
443 protected K key;
444 protected V value;
445 protected Node<K, V> next;
446
447 public K getKey() {
448 return key;
449 }
450
451 public V getValue() {
452 return value;
453 }
454
455 @Override
456 public int hashCode() {
457 return ((key == null ? 0 : key.hashCode()) ^
458 (value == null ? 0 : value.hashCode()));
459 }
460
461 @Override
462 public boolean equals(final Object obj) {
463 if (obj == this) {
464 return true;
465 }
466 if (obj instanceof Map.Entry<?, ?> == false) {
467 return false;
468 }
469
470 final Map.Entry<?, ?> e2 = (Map.Entry<?, ?>) obj;
471 return (
472 (key == null ? e2.getKey() == null : key.equals(e2.getKey())) &&
473 (value == null ? e2.getValue() == null : value.equals(e2.getValue())));
474 }
475
476 public V setValue(final V obj) {
477 final V retVal = value;
478 value = obj;
479 return retVal;
480 }
481 }
482
483 /**
484 * The lock object, which also includes a count of the nodes in this lock.
485 */
486 private final static class Lock {
487 public int size;
488 }
489
490 //-----------------------------------------------------------------------
491 private class BaseIterator {
492 private final ArrayList<Map.Entry<K, V>> current = new ArrayList<Map.Entry<K,V>>();
493 private int bucket;
494 private Map.Entry<K, V> last;
495
496 public boolean hasNext() {
497 if (current.size() > 0) {
498 return true;
499 }
500 while (bucket < buckets.length) {
501 synchronized (locks[bucket]) {
502 Node<K, V> n = buckets[bucket];
503 while (n != null) {
504 current.add(n);
505 n = n.next;
506 }
507 bucket++;
508 if (current.size() > 0) {
509 return true;
510 }
511 }
512 }
513 return false;
514 }
515
516 protected Map.Entry<K, V> nextEntry() {
517 if (!hasNext()) {
518 throw new NoSuchElementException();
519 }
520 last = current.remove(current.size() - 1);
521 return last;
522 }
523
524 public void remove() {
525 if (last == null) {
526 throw new IllegalStateException();
527 }
528 StaticBucketMap.this.remove(last.getKey());
529 last = null;
530 }
531 }
532
533 private class EntryIterator extends BaseIterator implements Iterator<Map.Entry<K, V>> {
534
535 public Map.Entry<K, V> next() {
536 return nextEntry();
537 }
538
539 }
540
541 private class ValueIterator extends BaseIterator implements Iterator<V> {
542
543 public V next() {
544 return nextEntry().getValue();
545 }
546
547 }
548
549 private class KeyIterator extends BaseIterator implements Iterator<K> {
550
551 public K next() {
552 return nextEntry().getKey();
553 }
554
555 }
556
557 private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
558
559 @Override
560 public int size() {
561 return StaticBucketMap.this.size();
562 }
563
564 @Override
565 public void clear() {
566 StaticBucketMap.this.clear();
567 }
568
569 @Override
570 public Iterator<Map.Entry<K, V>> iterator() {
571 return new EntryIterator();
572 }
573
574 @Override
575 public boolean contains(final Object obj) {
576 final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj;
577 final int hash = getHash(entry.getKey());
578 synchronized (locks[hash]) {
579 for (Node<K, V> n = buckets[hash]; n != null; n = n.next) {
580 if (n.equals(entry)) {
581 return true;
582 }
583 }
584 }
585 return false;
586 }
587
588 @Override
589 public boolean remove(final Object obj) {
590 if (obj instanceof Map.Entry<?, ?> == false) {
591 return false;
592 }
593 final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj;
594 final int hash = getHash(entry.getKey());
595 synchronized (locks[hash]) {
596 for (Node<K, V> n = buckets[hash]; n != null; n = n.next) {
597 if (n.equals(entry)) {
598 StaticBucketMap.this.remove(n.getKey());
599 return true;
600 }
601 }
602 }
603 return false;
604 }
605
606 }
607
608 private class KeySet extends AbstractSet<K> {
609
610 @Override
611 public int size() {
612 return StaticBucketMap.this.size();
613 }
614
615 @Override
616 public void clear() {
617 StaticBucketMap.this.clear();
618 }
619
620 @Override
621 public Iterator<K> iterator() {
622 return new KeyIterator();
623 }
624
625 @Override
626 public boolean contains(final Object obj) {
627 return StaticBucketMap.this.containsKey(obj);
628 }
629
630 @Override
631 public boolean remove(final Object obj) {
632 final int hash = getHash(obj);
633 synchronized (locks[hash]) {
634 for (Node<K, V> n = buckets[hash]; n != null; n = n.next) {
635 final Object k = n.getKey();
636 if ((k == obj) || ((k != null) && k.equals(obj))) {
637 StaticBucketMap.this.remove(k);
638 return true;
639 }
640 }
641 }
642 return false;
643 }
644
645 }
646
647
648 private class Values extends AbstractCollection<V> {
649
650 @Override
651 public int size() {
652 return StaticBucketMap.this.size();
653 }
654
655 @Override
656 public void clear() {
657 StaticBucketMap.this.clear();
658 }
659
660 @Override
661 public Iterator<V> iterator() {
662 return new ValueIterator();
663 }
664
665 }
666
667 /**
668 * Prevents any operations from occurring on this map while the
669 * given {@link Runnable} executes. This method can be used, for
670 * instance, to execute a bulk operation atomically:
671 *
672 * <pre>
673 * staticBucketMapInstance.atomic(new Runnable() {
674 * public void run() {
675 * staticBucketMapInstance.putAll(map);
676 * }
677 * });
678 * </pre>
679 *
680 * It can also be used if you need a reliable iterator:
681 *
682 * <pre>
683 * staticBucketMapInstance.atomic(new Runnable() {
684 * public void run() {
685 * Iterator iterator = staticBucketMapInstance.iterator();
686 * while (iterator.hasNext()) {
687 * foo(iterator.next();
688 * }
689 * }
690 * });
691 * </pre>
692 *
693 * <b>Implementation note:</b> This method requires a lot of time
694 * and a ton of stack space. Essentially a recursive algorithm is used
695 * to enter each bucket's monitor. If you have twenty thousand buckets
696 * in your map, then the recursive method will be invoked twenty thousand
697 * times. You have been warned.
698 *
699 * @param r the code to execute atomically
700 */
701 public void atomic(final Runnable r) {
702 if (r == null) {
703 throw new NullPointerException();
704 }
705 atomic(r, 0);
706 }
707
708 private void atomic(final Runnable r, final int bucket) {
709 if (bucket >= buckets.length) {
710 r.run();
711 return;
712 }
713 synchronized (locks[bucket]) {
714 atomic(r, bucket + 1);
715 }
716 }
717
718 }