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