Class MultiKeyMap<V>

java.lang.Object
com.cedarsoftware.util.MultiKeyMap<V>
Type Parameters:
V - the type of values stored in the map
All Implemented Interfaces:
ConcurrentMap<Object,V>, Map<Object,V>

public final class MultiKeyMap<V> extends Object implements ConcurrentMap<Object,V>
High-performance N-dimensional key-value Map implementation using separate chaining.

MultiKeyMap allows storing and retrieving values using multiple keys. Unlike traditional maps that use a single key, this map can handle keys with any number of components, making it ideal for complex lookup scenarios.

Key Features:

  • N-Dimensional Keys: Support for keys with any number of components (1, 2, 3, ... N).
  • High Performance: Zero-allocation polymorphic storage and optimized hash computation — no GC/heap used for "gets".
  • Thread-Safe: Lock-free reads with auto-tuned stripe locking that scales with your server, similar to ConcurrentHashMap.
  • Map Interface Compatible: Supports single-key operations via the standard Map interface (get()/put() automatically unpack Collections/Arrays (typed or Object[]) into multi-keys).
  • Flexible API: Var-args methods for convenient multi-key operations (getMultiKey()/putMultiKey() with many keys).
  • Smart Collection Handling: Configurable behavior for Collections — change the default automatic unpacking capability as needed.
  • N-Dimensional Array Expansion: Nested arrays of any depth are automatically flattened recursively into multi-keys.

Usage Examples:


 // Create a multi-key map
 MultiKeyMap<String> map = new MultiKeyMap<>(1024);

 // Store values with different key dimensions using Map interface
 map.put("single-key", "value1");                         // 1D key
 map.put(new Object[]{"k1", "k2"}, "value2");             // 2D key via array
 map.put(Arrays.asList("k1", "k2", "k3"), "value3");      // 3D key via Collection

 // OR use convenient varargs methods (requires MultiKeyMap variable type)
 MultiKeyMap<String> mkMap = new MultiKeyMap<>();
 mkMap.putMultiKey("value1", "single-key");               // 1D key
 mkMap.putMultiKey("value2", "key1", "key2");             // 2D key
 mkMap.putMultiKey("value3", "key1", "key2", "key3");     // 3D key
 mkMap.putMultiKey("value4", "k1", "k2", "k3", "k4");     // 4D key
 // ... unlimited dimensions

 // Retrieve values using matching signatures
 String val1 = map.get("single-key");
 String val2 = map.get(new Object[]{"k1", "k2"});
 String val3 = mkMap.getMultiKey("key1", "key2");
 String val4 = mkMap.getMultiKey("k1", "k2", "k3", "k4");
 

N-Dimensional Array Expansion:

MultiKeyMap automatically expands nested arrays of any depth into their constituent elements, providing powerful key flattening capabilities:


 MultiKeyMap<String> map = new MultiKeyMap<>();

 // 🔥 N-DIMENSIONAL ARRAY EXPANSION - The Ultimate Power Feature
 // Arrays and Collections expand with structure preservation - no limits on depth!

 // ✅ EQUIVALENT (same flat structure, cross-container support):
 String[] flatArray = {"config", "database", "url"};     // → ["config", "database", "url"]
 List&lt;String&gt; flatList = List.of("config", "database", "url");  // → ["config", "database", "url"]
 map.put(flatArray, "connection-string");
 // ALL of these work - cross-container equivalence:
 String result1 = map.get(flatArray);                     // ✅ Original String[]
 String result2 = map.get(flatList);                      // ✅ Equivalent List
 String result3 = map.getMultiKey("config", "database", "url");  // ✅ Individual elements

 // ❌ NOT EQUIVALENT (different structures preserved):
 String[][] nested2D = {{"config", "database"}, {"url", "port"}};     // → [🔒, ⬇, "config", "database", ⬆, ⬇, "url", "port", ⬆]
 String[] flat1D = {"config", "database", "url", "port"};             // → ["config", "database", "url", "port"]
 map.put(nested2D, "2D-structure");     // Stored with sentinels
 map.put(flat1D, "flat-structure");     // Stored without sentinels
 // These create SEPARATE entries:
 String val1 = map.get(nested2D);       // ✅ "2D-structure" (uses original array)
 String val2 = map.get(flat1D);         // ✅ "flat-structure" (uses original array)
 // Note: getMultiKey("config", "database", "url", "port") only retrieves flat1D
 

Collection and Array Handling:

MultiKeyMap provides flexible handling of Collections through the MultiKeyMap.CollectionKeyMode enum:

  • COLLECTIONS_EXPANDED: Collections are always unpacked into multi-key lookups (default)
  • COLLECTIONS_NOT_EXPANDED: Collections tried as single key first, fallback to unpacking

Arrays are ALWAYS expanded regardless of CollectionKeyMode setting because they lack meaningful equals()/hashCode() implementations and cannot serve as useful Map keys.


 // Configure collection handling behavior (affects Collections only, not Arrays)
 MultiKeyMap<String> map = new MultiKeyMap<>(1024, CollectionKeyMode.COLLECTIONS_NOT_EXPANDED);

 List<String> collectionKey = Arrays.asList("config", "database", "url");
 map.put(collectionKey, "jdbc:mysql://localhost:3306/db");  // Collection tried as single key first
 String url = map.get(collectionKey);                        // Retrieved as single key

 String[] arrayKey = {"config", "database", "url"};
 map.put(arrayKey, "another-value");                         // Array ALWAYS expanded to 3D key
 String value = map.get("config", "database", "url");       // Retrieved as 3D key
 

Performance Characteristics:

  • Time Complexity: O(1) average case for get/put/remove operations
  • Space Complexity: O(n) where n is the number of stored key-value pairs
  • Memory Efficiency: Polymorphic storage (Object vs Object[]) eliminates wrappers
  • Concurrency: Lock-free reads with auto-tuned stripe locking that scales with your server
  • Load Factor: Configurable, defaults to 0.75 for optimal performance

Thread Safety:

This implementation is fully thread-safe with enterprise-grade concurrency. Read operations (get, containsKey, etc.) are completely lock-free for maximum throughput. Write operations use auto-tuned stripe locking that scales with your server's cores, enabling multiple concurrent writers to operate simultaneously without contention. The stripe count auto-adapts to system cores (cores/2, minimum 8) for optimal performance across different hardware. Global operations (resize, clear) use coordinated locking to prevent deadlock while maintaining data consistency.

Author:
John DeRegnaucourt ([email protected])
Copyright (c) Cedar Software LLC

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

License

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
  • Constructor Details

    • MultiKeyMap

      public MultiKeyMap(int capacity, float loadFactor)
      Creates a new MultiKeyMap with the specified capacity and load factor.
      Parameters:
      capacity - the initial capacity
      loadFactor - the load factor threshold for resizing
    • MultiKeyMap

      public MultiKeyMap(int capacity, float loadFactor, MultiKeyMap.CollectionKeyMode collectionKeyMode)
      Creates a new MultiKeyMap with specified capacity, load factor, and collection key behavior.
      Parameters:
      capacity - the initial capacity
      loadFactor - the load factor threshold for resizing
      collectionKeyMode - how to handle Collections/Arrays in get/remove/containsKey operations
    • MultiKeyMap

      public MultiKeyMap(int capacity, float loadFactor, MultiKeyMap.CollectionKeyMode collectionKeyMode, boolean flattenDimensions)
      Creates a new MultiKeyMap with specified capacity, load factor, collection key behavior, and dimension flattening.
      Parameters:
      capacity - the initial capacity
      loadFactor - the load factor threshold for resizing
      collectionKeyMode - how to handle Collections/Arrays in get/remove/containsKey operations
      flattenDimensions - if true, flatten all dimensions (["a"] = [["a"]] = "a"); if false, preserve structure
    • MultiKeyMap

      public MultiKeyMap()
      Creates a new MultiKeyMap with default capacity (16) and default load factor (0.75).
    • MultiKeyMap

      public MultiKeyMap(MultiKeyMap.CollectionKeyMode collectionKeyMode, boolean flattenDimensions)
      Creates a new MultiKeyMap with default capacity (16), specified collection key behavior, and dimension flattening.
      Parameters:
      collectionKeyMode - how to handle Collections/Arrays in get/remove/containsKey operations
      flattenDimensions - if true, flatten all dimensions (["a"] = [["a"]] = "a"); if false, preserve structure
    • MultiKeyMap

      public MultiKeyMap(int capacity)
      Creates a new MultiKeyMap with the specified capacity and default load factor.
      Parameters:
      capacity - the initial capacity
    • MultiKeyMap

      public MultiKeyMap(boolean flattenDimensions)
      Creates a new MultiKeyMap with default capacity and dimension flattening.
      Parameters:
      flattenDimensions - if true, flatten all dimensions (["a"] = [["a"]] = "a"); if false, preserve structure
  • Method Details

    • getCollectionKeyMode

      public MultiKeyMap.CollectionKeyMode getCollectionKeyMode()
      Returns the CollectionKeyMode used by this MultiKeyMap.
      Returns:
      the CollectionKeyMode indicating how Collections are handled
    • getFlattenDimensions

      public boolean getFlattenDimensions()
      Returns whether this MultiKeyMap flattens dimensions.
      Returns:
      true if dimensions are flattened (["a"] = [["a"]] = "a"), false if structure is preserved
    • getMultiKey

      public V getMultiKey(Object... keys)
      Gets the conversion function for the given N-dimensional key, or null if not found. This method is lock-free for maximum read performance.
      Parameters:
      keys - the key components (can be varargs or Object[])
      Returns:
      the value associated with the key, or null if not found
    • get

      public V get(Object key)
      Map interface compatible get method with zero-allocation direct storage. Supports both single keys and N-dimensional keys via Object[] detection.
      Specified by:
      get in interface Map<Object,V>
      Parameters:
      key - either a single key or an Object[] containing multiple keys
      Returns:
      the value associated with the key, or null if not found
    • putMultiKey

      public V putMultiKey(V value, Object... keys)
      Premium var-args API - Store a value with unlimited multiple keys. This is the recommended API for MultiKeyMap users as it provides the best developer experience with unlimited keys and zero array allocations for inline arguments.

      Examples:

      
       MultiKeyMap<Employee> map = new MultiKeyMap<>();
      
       // Zero allocation - no arrays created
       map.put(employee, "dept", "engineering", "senior");
       map.put(person, "location", "building1", "floor2", "room101");
      
       // Works with existing arrays too
       String[] keyArray = {"dept", "marketing", "director"};
       map.put(manager, keyArray);  // Passes array directly to varargs
       
      Parameters:
      value - the value to store
      keys - the key components (unlimited number)
      Returns:
      the previous value associated with the key, or null if there was no mapping
    • put

      public V put(Object key, V value)
      Map interface compatible put method with auto-unpacking for arrays. This provides a great experience for Map users by automatically detecting and unpacking arrays into multi-key calls.

      Auto-unpacking behavior:

      • If key is an array → automatically unpacked into multiple keys
      • If key is a Collection → automatically unpacked into multiple keys
      • Otherwise → treated as single key

      Examples:

      
       Map<Object, Employee> map = new MultiKeyMap<>();
      
       // Auto-unpacking: array becomes multi-key
       String[] keys = {"dept", "engineering", "senior"};
       map.put(keys, employee);  // Stored as 3-key entry
      
       // Auto-unpacking: Collection becomes multi-key
       List<String> keyList = Arrays.asList("dept", "sales", "junior");
       map.put(keyList, employee);  // Stored as 3-key entry
      
       // Single key: other objects stored normally
       map.put("manager", boss);  // Stored as single-key entry
      
       // Typed arrays also auto-unpack
       int[] intKeys = {1, 2, 3};
       map.put(intKeys, data);  // Stored as 3-key entry
       
      Specified by:
      put in interface Map<Object,V>
      Parameters:
      key - single key, or array/Collection that will be auto-unpacked into multiple keys
      value - the value to store
      Returns:
      the previous value associated with the key, or null if there was no mapping
    • expandMultiDimensionalArray

      public static Object[] expandMultiDimensionalArray(Object sourceArrayOrCollection)
      Expands n-dimensional arrays and nested collections recursively into a flat Object[] array. Uses iterative approach with cycle detection to avoid infinite loops and recursion limits.

      This utility method is available for use by other classes that need n-dimensional array/collection expansion functionality, such as CaseInsensitiveMap.

      Parameters:
      sourceArrayOrCollection - The array or collection to expand (may contain nested structures)
      Returns:
      A flattened Object[] containing all elements from nested arrays/collections
    • size

      public int size()
      Returns the current number of entries in the map.
      Specified by:
      size in interface Map<Object,V>
    • getMaxChainLength

      public int getMaxChainLength()
      Returns the maximum chain length encountered so far.
    • getLoadFactor

      public double getLoadFactor()
      Returns the current load factor.
    • entries

      public Iterable<MultiKeyMap.MultiKeyEntry<V>> entries()
      Returns an iterator over all entries in the map. The iterator captures a snapshot of the current state and is thread-safe for reads. Concurrent modifications during iteration may not be reflected in the iteration.
    • isEmpty

      public boolean isEmpty()
      Returns true if this map contains no key-value mappings.
      Specified by:
      isEmpty in interface Map<Object,V>
    • containsValue

      public boolean containsValue(Object value)
      Returns true if this map maps one or more keys to the specified value.
      Specified by:
      containsValue in interface Map<Object,V>
    • removeMultiKey

      public V removeMultiKey(Object... keys)
      Removes the mapping for the specified N-dimensional key from this map if it is present.
      Parameters:
      keys - the key components (can be varargs)
      Returns:
      the previous value associated with the key, or null if there was no mapping
    • remove

      public V remove(Object key)
      Map interface compatible remove method with auto-unpacking for arrays and collections. This provides a great experience for Map users by automatically detecting and unpacking arrays/collections into multi-key calls.

      Auto-unpacking behavior:

      • If key is an array → automatically unpacked into multiple keys
      • If key is a Collection → automatically unpacked into multiple keys
      • Otherwise → treated as single key

      Examples:

      
       Map<Object, Employee> map = new MultiKeyMap<>();
      
       // Auto-unpacking: array becomes multi-key
       String[] keys = {"dept", "engineering", "senior"};
       Employee removed = map.remove(keys);  // Removes 3-key entry
      
       // Auto-unpacking: Collection becomes multi-key
       List<String> keyList = Arrays.asList("dept", "sales", "junior");
       Employee removed2 = map.remove(keyList);  // Removes 3-key entry
      
       // Single key: other objects removed normally
       Employee manager = map.remove("manager");  // Removes single-key entry
      
       // Typed arrays also auto-unpack
       int[] intKeys = {1, 2, 3};
       Data removed3 = map.remove(intKeys);  // Removes 3-key entry
       
      Specified by:
      remove in interface Map<Object,V>
      Parameters:
      key - single key, or array/Collection that will be auto-unpacked into multiple keys
      Returns:
      the previous value associated with the key, or null if there was no mapping
    • putAll

      public void putAll(Map<?,? extends V> m)
      Specified by:
      putAll in interface Map<Object,V>
    • putIfAbsent

      public V putIfAbsent(Object key, V value)

      Uses a double-check locking pattern to avoid unnecessary synchronization when a value is already present. If the key is absent or currently mapped to null, the provided value is stored.

      Specified by:
      putIfAbsent in interface ConcurrentMap<Object,V>
      Specified by:
      putIfAbsent in interface Map<Object,V>
      See Also:
    • computeIfAbsent

      public V computeIfAbsent(Object key, Function<? super Object,? extends V> mappingFunction)

      Performs a double-check locking pattern to avoid unnecessary synchronization when the value already exists. If the value is absent or null, the mapping function is invoked and the result stored if non-null.

      Specified by:
      computeIfAbsent in interface ConcurrentMap<Object,V>
      Specified by:
      computeIfAbsent in interface Map<Object,V>
      See Also:
    • computeIfPresent

      public V computeIfPresent(Object key, BiFunction<? super Object,? super V,? extends V> remappingFunction)

      Applies the remapping function if the specified key is present and currently mapped to a non-null value. The operation is performed under a single synchronization to ensure atomicity.

      Specified by:
      computeIfPresent in interface ConcurrentMap<Object,V>
      Specified by:
      computeIfPresent in interface Map<Object,V>
      See Also:
    • compute

      public V compute(Object key, BiFunction<? super Object,? super V,? extends V> remappingFunction)

      Computes a new mapping for the specified key using the given remapping function. The entire computation occurs while synchronized on the map's write lock to provide atomic behavior.

      Specified by:
      compute in interface ConcurrentMap<Object,V>
      Specified by:
      compute in interface Map<Object,V>
      See Also:
    • remove

      public boolean remove(Object key, Object value)
      Specified by:
      remove in interface ConcurrentMap<Object,V>
      Specified by:
      remove in interface Map<Object,V>
    • replace

      public V replace(Object key, V value)
      Specified by:
      replace in interface ConcurrentMap<Object,V>
      Specified by:
      replace in interface Map<Object,V>
    • replace

      public boolean replace(Object key, V oldValue, V newValue)
      Specified by:
      replace in interface ConcurrentMap<Object,V>
      Specified by:
      replace in interface Map<Object,V>
    • merge

      public V merge(Object key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction)

      If the specified key is not already associated with a value or is associated with null, associates it with the given non-null value. Otherwise, replaces the associated value with the results of the given remapping function, or removes if the result is null.

      Specified by:
      merge in interface ConcurrentMap<Object,V>
      Specified by:
      merge in interface Map<Object,V>
      See Also:
    • containsMultiKey

      public boolean containsMultiKey(Object... keys)
      Returns true if this map contains a mapping for the specified N-dimensional key.
      Parameters:
      keys - the key components (can be varargs)
      Returns:
      true if a mapping exists for the key
    • containsKey

      public boolean containsKey(Object key)
      Map interface compatible containsKey method. Supports both single keys and N-dimensional keys via Object[] detection. Uses efficient decision tree pattern: Normal objects first, then Arrays, then Collections.
      Specified by:
      containsKey in interface Map<Object,V>
      Parameters:
      key - either a single key or an Object[] containing multiple keys
      Returns:
      true if a mapping exists for the key
    • clear

      public void clear()
      Removes all the mappings from this map. The map will be empty after this call returns.
      Specified by:
      clear in interface Map<Object,V>
    • values

      public Collection<V> values()
      Returns a Collection view of the values contained in this map. The collection is backed by the map's current state snapshot.
      Specified by:
      values in interface Map<Object,V>
    • keySet

      public Set<Object> keySet()
      Returns a Set view of the keys contained in this map. For MultiKeyMap, keys can be single objects or Object[] arrays. The set is backed by the map's current state snapshot.
      Specified by:
      keySet in interface Map<Object,V>
    • entrySet

      public Set<Map.Entry<Object,V>> entrySet()
      Returns a Set view of the mappings contained in this map. Each entry represents a key-value mapping where the key can be a single object or an Object[] array for multi-dimensional keys.
      Specified by:
      entrySet in interface Map<Object,V>
    • hashCode

      public int hashCode()
      Returns the hash code value for this map. The hash code is computed based on all key-value pairs.
      Specified by:
      hashCode in interface Map<Object,V>
      Overrides:
      hashCode in class Object
    • equals

      public boolean equals(Object obj)
      Compares the specified object with this map for equality. Returns true if the given object is also a map and the two maps represent the same mappings.
      Specified by:
      equals in interface Map<Object,V>
      Overrides:
      equals in class Object
    • toString

      public String toString()
      Returns a string representation of this map. Shows the key-value mappings in the format {key1=value1, key2=value2}. Handles self-references to prevent infinite recursion.
      Overrides:
      toString in class Object
    • printContentionStatistics

      public void printContentionStatistics()
      Prints detailed contention statistics for debugging performance issues. Shows overall contention rates, stripe-level distribution, and global lock usage.