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.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 }