We have a situation where we are ending up using multi-level hash maps; that is, a hash map inside of a hash map, three or four levels deep.
Instinctively this felt wrong somewhere. I have read posts here that talks about how to iterate/use a multi-level hash map, but hardly any of them say what is the best practice for this.
Why are multi level hash maps bad, and what would be the better design, if any?
Here is the sample design of multi-level hash maps that we have:
Map<String, Object1> map1;
class Object1 {
String version;
Map<String,Object2> map2;
}
class Object2 {
Map<String,List<Object3>> map4;
Map<String,String> map5;
}
Use wrappers for composite HashMap keys Whenever a HashMap has composite String keys, use a wrapper instead of concatenating the strings to make a key. Doing so will make the lookup much faster and reduce allocation rate, as the benchmark below demonstrates.
HashMap, Hashtable, ConcurrentHashMap performance comparison If you notice ConcurrentHashMap is slightly slower performing than HashMap, however it's a 100% thread safe implementation. On the other hand Hashtable is also thread safe implementation, but it's 18 times slower than HashMap for this test scenario.
You must make sure: All of the updates to the HashMap are completed before the threads are instantiated and the thread that creates the map also forks the threads. The threads are only using the HashMap in read-only mode – either get() or iteration without remove. There are no threads updating the map.
You should use ConcurrentHashMap when you need very high concurrency in your project. It is thread safe without synchronizing the whole map . Reads can happen very fast while write is done with a lock. There is no locking at the object level.
As long as they're properly abstracted, it's not that big of a deal, but you lead yourself down some nasty rabbit holes in terms of readability. Without abstraction, maintaining this becomes a nightmare that no developer would wish on another.
Essentially, what you're creating is a table of sorts; the first key is the primary key to gain access to further columns. On a simple one, two, or three-level design, this isn't terrible; you need three keys to get a single value. Provided that there's a convenient way to access it, like the below, it's not a terrible idea (although there are far better out there).
public interface Table<K1, K2, K3, V> {
V get(K1 key1, K2 key2, K3 key3);
}
...However, it all depends on what you're actually doing with that data structure. If you find yourself attempting to iterate intermediate keys for values (that is, you're looking at Key 3 for the collection of all values between it and Key 5), you've got to rethink your business logic at that point. The data structure provided isn't flexible enough to handle all cases; more or less, it's used for simplistic indexing based on a set of values.
Alternatively, one could look into a Guava Table
, as that does the same sort of thing, with a better interface to it (something like the one I have above).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With