View Javadoc
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   * (<i>O(n)</i>).
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 <i>not</i> 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 <i>not</i>
81   * fail-fast.  They will <i>never</i> 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 <i>never</i> 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 (key == null ? e2.getKey() == null : key.equals(e2.getKey())) &&
287                 (value == null ? e2.getValue() == null : value.equals(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 obj) {
308             final V retVal = value;
309             value = obj;
310             return retVal;
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
387      *  given {@link Runnable} executes.  This method can be used, for
388      *  instance, to execute a bulk operation atomically:
389      *
390      *  <pre>
391      *    staticBucketMapInstance.atomic(new Runnable() {
392      *        public void run() {
393      *            staticBucketMapInstance.putAll(map);
394      *        }
395      *    });
396      *  </pre>
397      *
398      *  It can also be used if you need a reliable iterator:
399      *
400      *  <pre>
401      *    staticBucketMapInstance.atomic(new Runnable() {
402      *        public void run() {
403      *            Iterator iterator = staticBucketMapInstance.iterator();
404      *            while (iterator.hasNext()) {
405      *                foo(iterator.next();
406      *            }
407      *        }
408      *    });
409      *  </pre>
410      *
411      *  <b>Implementation note:</b> This method requires a lot of time
412      *  and a ton of stack space.  Essentially a recursive algorithm is used
413      *  to enter each bucket's monitor.  If you have twenty thousand buckets
414      *  in your map, then the recursive method will be invoked twenty thousand
415      *  times.  You have been warned.
416      *
417      *  @param runnable  the code to execute atomically
418      */
419     public void atomic(final Runnable runnable) {
420         atomic(Objects.requireNonNull(runnable, "runnable"), 0);
421     }
422 
423     private void atomic(final Runnable r, final int bucket) {
424         if (bucket >= buckets.length) {
425             r.run();
426             return;
427         }
428         synchronized (locks[bucket]) {
429             atomic(r, bucket + 1);
430         }
431     }
432 
433     /**
434      * Clears the map of all entries.
435      */
436     @Override
437     public void clear() {
438         for (int i = 0; i < buckets.length; i++) {
439             final Lock lock = locks[i];
440             synchronized (lock) {
441                 buckets[i] = null;
442                 lock.size = 0;
443             }
444         }
445     }
446 
447     /**
448      * Checks if the map contains the specified key.
449      *
450      * @param key  the key to check
451      * @return true if found
452      */
453     @Override
454     public boolean containsKey(final Object key) {
455         final int hash = getHash(key);
456 
457         synchronized (locks[hash]) {
458             Node<K, V> n = buckets[hash];
459 
460             while (n != null) {
461                 if (Objects.equals(n.key, key)) {
462                     return true;
463                 }
464 
465                 n = n.next;
466             }
467         }
468         return false;
469     }
470 
471     /**
472      * Checks if the map contains the specified value.
473      *
474      * @param value  the value to check
475      * @return true if found
476      */
477     @Override
478     public boolean containsValue(final Object value) {
479         for (int i = 0; i < buckets.length; i++) {
480             synchronized (locks[i]) {
481                 Node<K, V> n = buckets[i];
482 
483                 while (n != null) {
484                     if (Objects.equals(n.value, value)) {
485                         return true;
486                     }
487 
488                     n = n.next;
489                 }
490             }
491         }
492         return false;
493     }
494 
495     /**
496      * Gets the entry set.
497      *
498      * @return the entry set
499      */
500     @Override
501     public Set<Map.Entry<K, V>> entrySet() {
502         return new EntrySet();
503     }
504 
505     /**
506      * Compares this map to another, as per the Map specification.
507      *
508      * @param obj  the object to compare to
509      * @return true if equal
510      */
511     @Override
512     public boolean equals(final Object obj) {
513         if (obj == this) {
514             return true;
515         }
516         if (!(obj instanceof Map<?, ?>)) {
517             return false;
518         }
519         final Map<?, ?> other = (Map<?, ?>) obj;
520         return entrySet().equals(other.entrySet());
521     }
522 
523     /**
524      * Gets the value associated with the key.
525      *
526      * @param key  the key to retrieve
527      * @return the associated value
528      */
529     @Override
530     public V get(final Object key) {
531         final int hash = getHash(key);
532 
533         synchronized (locks[hash]) {
534             Node<K, V> n = buckets[hash];
535 
536             while (n != null) {
537                 if (Objects.equals(n.key, key)) {
538                     return n.value;
539                 }
540 
541                 n = n.next;
542             }
543         }
544         return null;
545     }
546 
547     /**
548      * Determine the exact hash entry for the key.  The hash algorithm
549      * is rather simplistic, but it does the job:
550      *
551      * <pre>
552      *   He = |Hk mod n|
553      * </pre>
554      *
555      * <p>
556      *   He is the entry's hashCode, Hk is the key's hashCode, and n is
557      *   the number of buckets.
558      * </p>
559      */
560     private int getHash(final Object key) {
561         if (key == null) {
562             return 0;
563         }
564         int hash = key.hashCode();
565         hash += ~(hash << 15);
566         hash ^= hash >>> 10;
567         hash += hash << 3;
568         hash ^= hash >>> 6;
569         hash += ~(hash << 11);
570         hash ^= hash >>> 16;
571         hash %= buckets.length;
572         return hash < 0 ? hash * -1 : hash;
573     }
574 
575     /**
576      * Gets the hash code, as per the Map specification.
577      *
578      * @return the hash code
579      */
580     @Override
581     public int hashCode() {
582         int hashCode = 0;
583 
584         for (int i = 0; i < buckets.length; i++) {
585             synchronized (locks[i]) {
586                 Node<K, V> n = buckets[i];
587 
588                 while (n != null) {
589                     hashCode += n.hashCode();
590                     n = n.next;
591                 }
592             }
593         }
594         return hashCode;
595     }
596 
597     /**
598      * Checks if the size is currently zero.
599      *
600      * @return true if empty
601      */
602     @Override
603     public boolean isEmpty() {
604         return size() == 0;
605     }
606 
607     /**
608      * Gets the key set.
609      *
610      * @return the key set
611      */
612     @Override
613     public Set<K> keySet() {
614         return new KeySet();
615     }
616 
617     /**
618      * Puts a new key value mapping into the map.
619      *
620      * @param key  the key to use
621      * @param value  the value to use
622      * @return the previous mapping for the key
623      */
624     @Override
625     public V put(final K key, final V value) {
626         final int hash = getHash(key);
627 
628         synchronized (locks[hash]) {
629             Node<K, V> n = buckets[hash];
630 
631             if (n == null) {
632                 n = new Node<>();
633                 n.key = key;
634                 n.value = value;
635                 buckets[hash] = n;
636                 locks[hash].size++;
637                 return null;
638             }
639 
640             // Set n to the last node in the linked list.  Check each key along the way
641             //  If the key is found, then change the value of that node and return
642             //  the old value.
643             for (Node<K, V> next = n; next != null; next = next.next) {
644                 n = next;
645 
646                 if (Objects.equals(n.key, key)) {
647                     final V returnVal = n.value;
648                     n.value = value;
649                     return returnVal;
650                 }
651             }
652 
653             // The key was not found in the current list of nodes, add it to the end
654             //  in a new node.
655             final Node<K, V> newNode = new Node<>();
656             newNode.key = key;
657             newNode.value = value;
658             n.next = newNode;
659             locks[hash].size++;
660         }
661         return null;
662     }
663 
664     /**
665      * Puts all the entries from the specified map into this map.
666      * This operation is <b>not atomic</b> and may have undesired effects.
667      *
668      * @param map  the map of entries to add
669      */
670     @Override
671     public void putAll(final Map<? extends K, ? extends V> map) {
672         for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
673             put(entry.getKey(), entry.getValue());
674         }
675     }
676 
677     /**
678      * Removes the specified key from the map.
679      *
680      * @param key  the key to remove
681      * @return the previous value at this key
682      */
683     @Override
684     public V remove(final Object key) {
685         final int hash = getHash(key);
686 
687         synchronized (locks[hash]) {
688             Node<K, V> n = buckets[hash];
689             Node<K, V> prev = null;
690 
691             while (n != null) {
692                 if (Objects.equals(n.key, key)) {
693                     // Remove this node from the linked list of nodes.
694                     if (null == prev) {
695                         // This node was the head, set the next node to be the new head.
696                         buckets[hash] = n.next;
697                     } else {
698                         // Set the next node of the previous node to be the node after this one.
699                         prev.next = n.next;
700                     }
701                     locks[hash].size--;
702                     return n.value;
703                 }
704 
705                 prev = n;
706                 n = n.next;
707             }
708         }
709         return null;
710     }
711 
712     /**
713      * Gets the current size of the map.
714      * The value is computed fresh each time the method is called.
715      *
716      * @return the current size
717      */
718     @Override
719     public int size() {
720         int cnt = 0;
721 
722         for (int i = 0; i < buckets.length; i++) {
723             synchronized (locks[i]) {
724                 cnt += locks[i].size;
725             }
726         }
727         return cnt;
728     }
729 
730     /**
731      * Gets the values.
732      *
733      * @return the values
734      */
735     @Override
736     public Collection<V> values() {
737         return new Values();
738     }
739 
740 }