Map of get vs containskey efficient check?

2.3K    Asked by CameronOliver in Salesforce , Asked on Jul 12, 2021
 I have map Map sampleMap ;

I have little confusion on the two map methods. i got a review saying that to use containsKey method rather than get method. But I am thinking in a way that the get will also check if the value is null or not ?

if(sampleMap.get('XXX') != null ) { //do stuff } if(sampleMap.containsKey('XXX')) { //do stuff }

Suggest me !


Answered by Clare Matthews

containsKey and get have the same performance characteristics. The majority of the CPU time used will be within the hashCode and equals methods for the objects used as keys. Poorly designed keys will have poor performance characteristics, particularly when you're using Custom Types in map methods in salesforce.

In practice, I've found that the majority of the time, you'll need to use the results that you get back from the map, so it is important that you cache the results of get so that you're not using containsKey or get more frequently than necessary. In other words, the optimal design choice usually look like this:

    // Replace "Object" with the appropriate data type Object value = someMap.get(key); if(value != null) { // Do something with value }

This pattern means you won't need to call both containsKey and get, which reduces CPU time, sometimes significantly. As I stated above, make sure that, if you're using custom types for keys, that non-equal values return unique hashCode values.

For example, this is a bad key design:

    public class Key { public Integer hashCode() { return 1; } public Boolean equals(Object o) { return o === this; } }

This is because the number of equals methods that will be called is up to the number of elements in the set or map methods in salesforce key set, which can easily be hundreds of times more costly than an optimal key design:

    public class Key { static Integer counter = 0; Integer code; public Key() { code = counter++; } public Integer hashCode() { return code; } public Boolean equals(Object o) { return o === this; } }

By returning unique hashCode values for values which are not equal, you'll drastically improve the performance of both get and containsKey.

Keep in mind that most of the time, you don't care if a map contains a particular key, you care about the value being returned having a null value (to avoid NullPointerException). It is incredibly rare that you'll ever only care about the presence of a key in a map (containsKey), and it is extremely common that you'll care about NullPointerException.

So, in conclusion, I would say that the most efficient design pattern will almost always be to cache the results of get, and not use containsKey. containsKey is almost always just a CPU sink, wasting precious CPU cycles when you're just going to be calling get anyways. It takes approximately the same amount of time to call get and containsKey, and it's virtually guaranteed that after you call containsKey, you're going to call get anyways, so you may as well cut out the middle man.

If you write code that only uses containsKey and doesn't use get, you should be using a Set, not a Map. If you're using containsKey and get, you're probably using a sub-optimal algorithm. You should call get no more than once per key for optimal performance.

The rare case for using containsKey used to be when initializing lists or sets in maps, like this:

    Map contacts = new Map(); for(Contact record: [SELECT AccountId FROM Contact]) { if(contacts.containsKey(record.AccountId)) { contacts.get(record.AccountId).add(record); } else { contacts.put(record.AccountId, new List { record }); } }

However, in the past year or so, I've designed an even more optimal design:

    Contact[] temp; Map contacts = new map(); for(Contact record: [SELECT AccountId FROM Contact]) { if((temp = contacts.get(record.AccountId)) == null) { contacts.put(record.AccountId, temp = new Contact[0]); } temp.add(record); }

While you'll want to add comments to explain this, this solution combines a null check with caching to produce optimal CPU usage. When you want to aggregate data together this way, this combined caching strategy outperforms any other design I've written, seen or used by a large margin, particularly when you need the performance boost in triggers.



Your Answer

Answer (1)

When considering the efficiency of using get(key) versus containsKey(key) in a map data structure, such as HashMap or TreeMap, it's important to understand their underlying behaviors and performance characteristics:

  get(key) Method:

Behavior: Retrieves the value associated with the specified key if the key is present in the map; returns null if the key is not found.

Efficiency:

HashMap and LinkedHashMap:

Average-case time complexity is O(1) for retrieval due to hash-based lookup.

In rare cases, when there are hash collisions, it can degrade to O(n), but this is uncommon with a good hash function and proper load factor.

TreeMap:

  • Average-case time complexity is O(log n) due to its Red-Black Tree structure.
  • Guaranteed O(log n) performance ensures consistent behavior regardless of hash collisions.

containsKey(key) Method:

Behavior: Checks whether the map contains the specified key.

Efficiency:

HashMap and LinkedHashMap:

Average-case time complexity is O(1) due to hash-based lookup.

Similar to get(key), hash collisions are rare and can lead to O(n) in the worst case.

TreeMap:

Average-case time complexity is O(log n) due to tree traversal to check for the key's presence.

Key Differences and Considerations:

Use Cases:

Use get(key) when you need to retrieve the associated value for a key, as it directly returns the value if present.

Use containsKey(key) when you only need to check for the presence of the key without retrieving its associated value.

Performance Insights:

Both methods are efficient (O(1) or O(log n) depending on the map type), but get(key) involves retrieving the value, making it slightly heavier than containsKey(key), which only checks for existence.

Null Handling:

get(key) returns null if the key is not found or if the value associated with the key is null.

containsKey(key) returns true or false based solely on the presence of the key, irrespective of the associated value.

Implementation Differences:

get(key) is designed for retrieval and may involve additional operations to return the value, whereas containsKey(key) performs a straightforward existence check.

Example Usage:

  Map map = new HashMap<>();map.put("apple", 10);map.put("banana", 20);// Using get(key)Integer value1 = map.get("apple"); // returns 10Integer value2 = map.get("grape"); // returns null// Using containsKey(key)boolean containsApple = map.containsKey("apple"); // returns trueboolean containsGrape = map.containsKey("grape"); // returns false

Conclusion:

Efficiency: Both get(key) and containsKey(key) are generally efficient operations in hashmap implementations (O(1) average case). In tree-based maps like TreeMap, they are slightly heavier (O(log n) average case) due to tree traversal.

Choice: Choose based on whether you need to retrieve the associated value (get(key)) or simply check for key existence (containsKey(key)).

Performance Impact: For large datasets or critical performance scenarios, consider benchmarking or profiling to determine any noticeable performance differences between get(key) and containsKey(key) operations based on your specific usage patterns.

4 Months

Interviews

Parent Categories