StaticBucketMap.java

  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. import java.util.AbstractCollection;
  19. import java.util.AbstractSet;
  20. import java.util.ArrayList;
  21. import java.util.Collection;
  22. import java.util.Iterator;
  23. import java.util.Map;
  24. import java.util.NoSuchElementException;
  25. import java.util.Objects;
  26. import java.util.Set;

  27. import org.apache.commons.collections4.KeyValue;

  28. /**
  29.  * A StaticBucketMap is an efficient, thread-safe implementation of
  30.  * {@link java.util.Map} that performs well in a highly
  31.  * thread-contentious environment.
  32.  * <p>
  33.  * 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.  * (<em>O(n)</em>).
  42.  * </p>
  43.  * <p>
  44.  * Each bucket in the hash table has its own monitor, so two threads can
  45.  * safely operate on the map at the same time, often without incurring any
  46.  * monitor contention.  This means that you don't have to wrap instances
  47.  * of this class with {@link java.util.Collections#synchronizedMap(Map)};
  48.  * instances are already thread-safe.  Unfortunately, however, this means
  49.  * that this map implementation behaves in ways you may find disconcerting.
  50.  * Bulk operations, such as {@link #putAll(Map) putAll} or the
  51.  * {@link Collection#retainAll(Collection) retainAll} operation in collection
  52.  * views, are <em>not</em> atomic.  If two threads are simultaneously
  53.  * executing
  54.  * </p>
  55.  *
  56.  * <pre>
  57.  *   staticBucketMapInstance.putAll(map);
  58.  * </pre>
  59.  *
  60.  * and
  61.  *
  62.  * <pre>
  63.  *   staticBucketMapInstance.entrySet().removeAll(map.entrySet());
  64.  * </pre>
  65.  *
  66.  * <p>
  67.  * then the results are generally random.  Those two statement could cancel
  68.  * each other out, leaving {@code staticBucketMapInstance} essentially
  69.  * unchanged, or they could leave some random subset of {@code map} in
  70.  * {@code staticBucketMapInstance}.
  71.  * </p>
  72.  * <p>
  73.  * Also, much like an encyclopedia, the results of {@link #size()} and
  74.  * {@link #isEmpty()} are out-of-date as soon as they are produced.
  75.  * </p>
  76.  * <p>
  77.  * The iterators returned by the collection views of this class are <em>not</em>
  78.  * fail-fast.  They will <em>never</em> raise a
  79.  * {@link java.util.ConcurrentModificationException}.  Keys and values
  80.  * added to the map after the iterator is created do not necessarily appear
  81.  * during iteration.  Similarly, the iterator does not necessarily fail to
  82.  * return keys and values that were removed after the iterator was created.
  83.  * </p>
  84.  * <p>
  85.  * Finally, unlike {@link java.util.HashMap}-style implementations, this
  86.  * class <em>never</em> rehashes the map.  The number of buckets is fixed
  87.  * at construction time and never altered.  Performance may degrade if
  88.  * you do not allocate enough buckets upfront.
  89.  * </p>
  90.  * <p>
  91.  * The {@link #atomic(Runnable)} method is provided to allow atomic iterations
  92.  * and bulk operations; however, overuse of {@link #atomic(Runnable) atomic}
  93.  * will basically result in a map that's slower than an ordinary synchronized
  94.  * {@link java.util.HashMap}.
  95.  * </p>
  96.  * <p>
  97.  * Use this class if you do not require reliable bulk operations and
  98.  * iterations, or if you can make your own guarantees about how bulk
  99.  * operations will affect the map.
  100.  * </p>
  101.  *
  102.  * @param <K> the type of the keys in this map
  103.  * @param <V> the type of the values in this map
  104.  * @since 3.0 (previously in main package v2.1)
  105.  */
  106. public final class StaticBucketMap<K, V> extends AbstractIterableMap<K, V> {

  107.     class BaseIterator {
  108.         private final ArrayList<Map.Entry<K, V>> current = new ArrayList<>();
  109.         private int bucket;
  110.         private Map.Entry<K, V> last;

  111.         public boolean hasNext() {
  112.             if (!current.isEmpty()) {
  113.                 return true;
  114.             }
  115.             while (bucket < buckets.length) {
  116.                 synchronized (locks[bucket]) {
  117.                     Node<K, V> n = buckets[bucket];
  118.                     while (n != null) {
  119.                         current.add(n);
  120.                         n = n.next;
  121.                     }
  122.                     bucket++;
  123.                     if (!current.isEmpty()) {
  124.                         return true;
  125.                     }
  126.                 }
  127.             }
  128.             return false;
  129.         }

  130.         protected Map.Entry<K, V> nextEntry() {
  131.             if (!hasNext()) {
  132.                 throw new NoSuchElementException();
  133.             }
  134.             last = current.remove(current.size() - 1);
  135.             return last;
  136.         }

  137.         public void remove() {
  138.             if (last == null) {
  139.                 throw new IllegalStateException();
  140.             }
  141.             StaticBucketMap.this.remove(last.getKey());
  142.             last = null;
  143.         }
  144.     }

  145.     private final class EntryIterator extends BaseIterator implements Iterator<Map.Entry<K, V>> {

  146.         @Override
  147.         public Map.Entry<K, V> next() {
  148.             return nextEntry();
  149.         }

  150.     }

  151.     private final class EntrySet extends AbstractSet<Map.Entry<K, V>> {

  152.         @Override
  153.         public void clear() {
  154.             StaticBucketMap.this.clear();
  155.         }

  156.         @Override
  157.         public boolean contains(final Object obj) {
  158.             final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj;
  159.             final int hash = getHash(entry.getKey());
  160.             synchronized (locks[hash]) {
  161.                 for (Node<K, V> n = buckets[hash]; n != null; n = n.next) {
  162.                     if (n.equals(entry)) {
  163.                         return true;
  164.                     }
  165.                 }
  166.             }
  167.             return false;
  168.         }

  169.         @Override
  170.         public Iterator<Map.Entry<K, V>> iterator() {
  171.             return new EntryIterator();
  172.         }

  173.         @Override
  174.         public boolean remove(final Object obj) {
  175.             if (!(obj instanceof Map.Entry<?, ?>)) {
  176.                 return false;
  177.             }
  178.             final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj;
  179.             final int hash = getHash(entry.getKey());
  180.             synchronized (locks[hash]) {
  181.                 for (Node<K, V> n = buckets[hash]; n != null; n = n.next) {
  182.                     if (n.equals(entry)) {
  183.                         StaticBucketMap.this.remove(n.getKey());
  184.                         return true;
  185.                     }
  186.                 }
  187.             }
  188.             return false;
  189.         }

  190.         @Override
  191.         public int size() {
  192.             return StaticBucketMap.this.size();
  193.         }

  194.     }

  195.     private final class KeyIterator extends BaseIterator implements Iterator<K> {

  196.         @Override
  197.         public K next() {
  198.             return nextEntry().getKey();
  199.         }

  200.     }

  201.     private final class KeySet extends AbstractSet<K> {

  202.         @Override
  203.         public void clear() {
  204.             StaticBucketMap.this.clear();
  205.         }

  206.         @Override
  207.         public boolean contains(final Object obj) {
  208.             return StaticBucketMap.this.containsKey(obj);
  209.         }

  210.         @Override
  211.         public Iterator<K> iterator() {
  212.             return new KeyIterator();
  213.         }

  214.         @Override
  215.         public boolean remove(final Object obj) {
  216.             final int hash = getHash(obj);
  217.             synchronized (locks[hash]) {
  218.                 for (Node<K, V> n = buckets[hash]; n != null; n = n.next) {
  219.                     final Object k = n.getKey();
  220.                     if (Objects.equals(k, obj)) {
  221.                         StaticBucketMap.this.remove(k);
  222.                         return true;
  223.                     }
  224.                 }
  225.             }
  226.             return false;
  227.         }

  228.         @Override
  229.         public int size() {
  230.             return StaticBucketMap.this.size();
  231.         }

  232.     }

  233.     /**
  234.      * The lock object, which also includes a count of the nodes in this lock.
  235.      */
  236.     private static final class Lock {
  237.         public int size;
  238.     }

  239.     /**
  240.      * The Map.Entry for the StaticBucketMap.
  241.      */
  242.     private static final class Node<K, V> implements Map.Entry<K, V>, KeyValue<K, V> {
  243.         protected K key;
  244.         protected V value;
  245.         protected Node<K, V> next;

  246.         @Override
  247.         public boolean equals(final Object obj) {
  248.             if (obj == this) {
  249.                 return true;
  250.             }
  251.             if (!(obj instanceof Map.Entry<?, ?>)) {
  252.                 return false;
  253.             }

  254.             final Map.Entry<?, ?> e2 = (Map.Entry<?, ?>) obj;
  255.             return Objects.equals(key, e2.getKey()) &&
  256.                    Objects.equals(value, e2.getValue());
  257.         }

  258.         @Override
  259.         public K getKey() {
  260.             return key;
  261.         }

  262.         @Override
  263.         public V getValue() {
  264.             return value;
  265.         }

  266.         @Override
  267.         public int hashCode() {
  268.             return (key == null ? 0 : key.hashCode()) ^
  269.                     (value == null ? 0 : value.hashCode());
  270.         }

  271.         @Override
  272.         public V setValue(final V value) {
  273.             final V old = this.value;
  274.             this.value = value;
  275.             return old;
  276.         }
  277.     }

  278.     private final class ValueIterator extends BaseIterator implements Iterator<V> {

  279.         @Override
  280.         public V next() {
  281.             return nextEntry().getValue();
  282.         }

  283.     }

  284.     private final class Values extends AbstractCollection<V> {

  285.         @Override
  286.         public void clear() {
  287.             StaticBucketMap.this.clear();
  288.         }

  289.         @Override
  290.         public Iterator<V> iterator() {
  291.             return new ValueIterator();
  292.         }

  293.         @Override
  294.         public int size() {
  295.             return StaticBucketMap.this.size();
  296.         }

  297.     }

  298.     /** The default number of buckets to use */
  299.     private static final int DEFAULT_BUCKETS = 255;

  300.     /** The array of buckets, where the actual data is held */
  301.     private final Node<K, V>[] buckets;

  302.     /** The matching array of locks */
  303.     private final Lock[] locks;

  304.     /**
  305.      * Initializes the map with the default number of buckets (255).
  306.      */
  307.     public StaticBucketMap() {
  308.         this(DEFAULT_BUCKETS);
  309.     }

  310.     /**
  311.      * Initializes the map with a specified number of buckets.  The number
  312.      * of buckets is never below 17, and is always an odd number (StaticBucketMap
  313.      * ensures this). The number of buckets is inversely proportional to the
  314.      * chances for thread contention.  The fewer buckets, the more chances for
  315.      * thread contention.  The more buckets the fewer chances for thread
  316.      * contention.
  317.      *
  318.      * @param numBuckets  the number of buckets for this map
  319.      */
  320.     @SuppressWarnings("unchecked")
  321.     public StaticBucketMap(final int numBuckets) {
  322.         int size = Math.max(17, numBuckets);

  323.         // Ensure that bucketSize is never a power of 2 (to ensure maximal distribution)
  324.         if (size % 2 == 0) {
  325.             size--;
  326.         }

  327.         buckets = new Node[size];
  328.         locks = new Lock[size];

  329.         for (int i = 0; i < size; i++) {
  330.             locks[i] = new Lock();
  331.         }
  332.     }

  333.     /**
  334.      * 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
  335.      * operation atomically:
  336.      * <pre>
  337.      * staticBucketMapInstance.atomic(new Runnable() {
  338.      *     public void run() {
  339.      *         staticBucketMapInstance.putAll(map);
  340.      *     }
  341.      * });
  342.      * </pre>
  343.      * <p>
  344.      * It can also be used if you need a reliable iterator:
  345.      * </p>
  346.      *
  347.      * <pre>
  348.      *    staticBucketMapInstance.atomic(new Runnable() {
  349.      *        public void run() {
  350.      *            Iterator iterator = staticBucketMapInstance.iterator();
  351.      *            while (iterator.hasNext()) {
  352.      *                foo(iterator.next();
  353.      *            }
  354.      *        }
  355.      *    });
  356.      * </pre>
  357.      * <p>
  358.      * <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
  359.      * monitor. If you have twenty thousand buckets in your map, then the recursive method will be invoked twenty thousand times. You have been warned.
  360.      * </p>
  361.      *
  362.      * @param runnable the code to execute atomically
  363.      */
  364.     public void atomic(final Runnable runnable) {
  365.         atomic(Objects.requireNonNull(runnable, "runnable"), 0);
  366.     }

  367.     private void atomic(final Runnable r, final int bucket) {
  368.         if (bucket >= buckets.length) {
  369.             r.run();
  370.             return;
  371.         }
  372.         synchronized (locks[bucket]) {
  373.             atomic(r, bucket + 1);
  374.         }
  375.     }

  376.     /**
  377.      * Clears the map of all entries.
  378.      */
  379.     @Override
  380.     public void clear() {
  381.         for (int i = 0; i < buckets.length; i++) {
  382.             final Lock lock = locks[i];
  383.             synchronized (lock) {
  384.                 buckets[i] = null;
  385.                 lock.size = 0;
  386.             }
  387.         }
  388.     }

  389.     /**
  390.      * Checks if the map contains the specified key.
  391.      *
  392.      * @param key  the key to check
  393.      * @return true if found
  394.      */
  395.     @Override
  396.     public boolean containsKey(final Object key) {
  397.         final int hash = getHash(key);

  398.         synchronized (locks[hash]) {
  399.             Node<K, V> n = buckets[hash];

  400.             while (n != null) {
  401.                 if (Objects.equals(n.key, key)) {
  402.                     return true;
  403.                 }

  404.                 n = n.next;
  405.             }
  406.         }
  407.         return false;
  408.     }

  409.     /**
  410.      * Checks if the map contains the specified value.
  411.      *
  412.      * @param value  the value to check
  413.      * @return true if found
  414.      */
  415.     @Override
  416.     public boolean containsValue(final Object value) {
  417.         for (int i = 0; i < buckets.length; i++) {
  418.             synchronized (locks[i]) {
  419.                 Node<K, V> n = buckets[i];

  420.                 while (n != null) {
  421.                     if (Objects.equals(n.value, value)) {
  422.                         return true;
  423.                     }

  424.                     n = n.next;
  425.                 }
  426.             }
  427.         }
  428.         return false;
  429.     }

  430.     /**
  431.      * Gets the entry set.
  432.      *
  433.      * @return the entry set
  434.      */
  435.     @Override
  436.     public Set<Map.Entry<K, V>> entrySet() {
  437.         return new EntrySet();
  438.     }

  439.     /**
  440.      * Compares this map to another, as per the Map specification.
  441.      *
  442.      * @param obj  the object to compare to
  443.      * @return true if equal
  444.      */
  445.     @Override
  446.     public boolean equals(final Object obj) {
  447.         if (obj == this) {
  448.             return true;
  449.         }
  450.         if (!(obj instanceof Map<?, ?>)) {
  451.             return false;
  452.         }
  453.         final Map<?, ?> other = (Map<?, ?>) obj;
  454.         return entrySet().equals(other.entrySet());
  455.     }

  456.     /**
  457.      * Gets the value associated with the key.
  458.      *
  459.      * @param key  the key to retrieve
  460.      * @return the associated value
  461.      */
  462.     @Override
  463.     public V get(final Object key) {
  464.         final int hash = getHash(key);

  465.         synchronized (locks[hash]) {
  466.             Node<K, V> n = buckets[hash];

  467.             while (n != null) {
  468.                 if (Objects.equals(n.key, key)) {
  469.                     return n.value;
  470.                 }

  471.                 n = n.next;
  472.             }
  473.         }
  474.         return null;
  475.     }

  476.     /**
  477.      * Determine the exact hash entry for the key.  The hash algorithm
  478.      * is rather simplistic, but it does the job:
  479.      *
  480.      * <pre>
  481.      *   He = |Hk mod n|
  482.      * </pre>
  483.      *
  484.      * <p>
  485.      *   He is the entry's hashCode, Hk is the key's hashCode, and n is
  486.      *   the number of buckets.
  487.      * </p>
  488.      */
  489.     private int getHash(final Object key) {
  490.         if (key == null) {
  491.             return 0;
  492.         }
  493.         int hash = key.hashCode();
  494.         hash += ~(hash << 15);
  495.         hash ^= hash >>> 10;
  496.         hash += hash << 3;
  497.         hash ^= hash >>> 6;
  498.         hash += ~(hash << 11);
  499.         hash ^= hash >>> 16;
  500.         hash %= buckets.length;
  501.         return hash < 0 ? hash * -1 : hash;
  502.     }

  503.     /**
  504.      * Gets the hash code, as per the Map specification.
  505.      *
  506.      * @return the hash code
  507.      */
  508.     @Override
  509.     public int hashCode() {
  510.         int hashCode = 0;

  511.         for (int i = 0; i < buckets.length; i++) {
  512.             synchronized (locks[i]) {
  513.                 Node<K, V> n = buckets[i];

  514.                 while (n != null) {
  515.                     hashCode += n.hashCode();
  516.                     n = n.next;
  517.                 }
  518.             }
  519.         }
  520.         return hashCode;
  521.     }

  522.     /**
  523.      * Checks if the size is currently zero.
  524.      *
  525.      * @return true if empty
  526.      */
  527.     @Override
  528.     public boolean isEmpty() {
  529.         return size() == 0;
  530.     }

  531.     /**
  532.      * Gets the key set.
  533.      *
  534.      * @return the key set
  535.      */
  536.     @Override
  537.     public Set<K> keySet() {
  538.         return new KeySet();
  539.     }

  540.     /**
  541.      * Puts a new key value mapping into the map.
  542.      *
  543.      * @param key  the key to use
  544.      * @param value  the value to use
  545.      * @return the previous mapping for the key
  546.      */
  547.     @Override
  548.     public V put(final K key, final V value) {
  549.         final int hash = getHash(key);

  550.         synchronized (locks[hash]) {
  551.             Node<K, V> n = buckets[hash];

  552.             if (n == null) {
  553.                 n = new Node<>();
  554.                 n.key = key;
  555.                 n.value = value;
  556.                 buckets[hash] = n;
  557.                 locks[hash].size++;
  558.                 return null;
  559.             }

  560.             // Set n to the last node in the linked list.  Check each key along the way
  561.             //  If the key is found, then change the value of that node and return
  562.             //  the old value.
  563.             for (Node<K, V> next = n; next != null; next = next.next) {
  564.                 n = next;

  565.                 if (Objects.equals(n.key, key)) {
  566.                     final V returnVal = n.value;
  567.                     n.value = value;
  568.                     return returnVal;
  569.                 }
  570.             }

  571.             // The key was not found in the current list of nodes, add it to the end
  572.             //  in a new node.
  573.             final Node<K, V> newNode = new Node<>();
  574.             newNode.key = key;
  575.             newNode.value = value;
  576.             n.next = newNode;
  577.             locks[hash].size++;
  578.         }
  579.         return null;
  580.     }

  581.     /**
  582.      * Puts all the entries from the specified map into this map.
  583.      * This operation is <strong>not atomic</strong> and may have undesired effects.
  584.      *
  585.      * @param map  the map of entries to add
  586.      */
  587.     @Override
  588.     public void putAll(final Map<? extends K, ? extends V> map) {
  589.         for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
  590.             put(entry.getKey(), entry.getValue());
  591.         }
  592.     }

  593.     /**
  594.      * Removes the specified key from the map.
  595.      *
  596.      * @param key  the key to remove
  597.      * @return the previous value at this key
  598.      */
  599.     @Override
  600.     public V remove(final Object key) {
  601.         final int hash = getHash(key);

  602.         synchronized (locks[hash]) {
  603.             Node<K, V> n = buckets[hash];
  604.             Node<K, V> prev = null;

  605.             while (n != null) {
  606.                 if (Objects.equals(n.key, key)) {
  607.                     // Remove this node from the linked list of nodes.
  608.                     if (null == prev) {
  609.                         // This node was the head, set the next node to be the new head.
  610.                         buckets[hash] = n.next;
  611.                     } else {
  612.                         // Set the next node of the previous node to be the node after this one.
  613.                         prev.next = n.next;
  614.                     }
  615.                     locks[hash].size--;
  616.                     return n.value;
  617.                 }

  618.                 prev = n;
  619.                 n = n.next;
  620.             }
  621.         }
  622.         return null;
  623.     }

  624.     /**
  625.      * Gets the current size of the map.
  626.      * The value is computed fresh each time the method is called.
  627.      *
  628.      * @return the current size
  629.      */
  630.     @Override
  631.     public int size() {
  632.         int cnt = 0;

  633.         for (int i = 0; i < buckets.length; i++) {
  634.             synchronized (locks[i]) {
  635.                 cnt += locks[i].size;
  636.             }
  637.         }
  638.         return cnt;
  639.     }

  640.     /**
  641.      * Gets the values.
  642.      *
  643.      * @return the values
  644.      */
  645.     @Override
  646.     public Collection<V> values() {
  647.         return new Values();
  648.     }

  649. }