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