Most Efficient Way to Increment a Map Value in Java

Most efficient way to increment a Map value in Java

Some test results

I've gotten a lot of good answers to this question--thanks folks--so I decided to run some tests and figure out which method is actually fastest. The five methods I tested are these:

  • the "ContainsKey" method that I presented in the question
  • the "TestForNull" method suggested by Aleksandar Dimitrov
  • the "AtomicLong" method suggested by Hank Gay
  • the "Trove" method suggested by jrudolph
  • the "MutableInt" method suggested by phax.myopenid.com

Method

Here's what I did...

  1. created five classes that were identical except for the differences shown below. Each class had to perform an operation typical of the scenario I presented: opening a 10MB file and reading it in, then performing a frequency count of all the word tokens in the file. Since this took an average of only 3 seconds, I had it perform the frequency count (not the I/O) 10 times.
  2. timed the loop of 10 iterations but not the I/O operation and recorded the total time taken (in clock seconds) essentially using Ian Darwin's method in the Java Cookbook.
  3. performed all five tests in series, and then did this another three times.
  4. averaged the four results for each method.

Results

I'll present the results first and the code below for those who are interested.

The ContainsKey method was, as expected, the slowest, so I'll give the speed of each method in comparison to the speed of that method.

  • ContainsKey: 30.654 seconds (baseline)
  • AtomicLong: 29.780 seconds (1.03 times as fast)
  • TestForNull: 28.804 seconds (1.06 times as fast)
  • Trove: 26.313 seconds (1.16 times as fast)
  • MutableInt: 25.747 seconds (1.19 times as fast)

Conclusions

It would appear that only the MutableInt method and the Trove method are significantly faster, in that only they give a performance boost of more than 10%. However, if threading is an issue, AtomicLong might be more attractive than the others (I'm not really sure). I also ran TestForNull with final variables, but the difference was negligible.

Note that I haven't profiled memory usage in the different scenarios. I'd be happy to hear from anybody who has good insights into how the MutableInt and Trove methods would be likely to affect memory usage.

Personally, I find the MutableInt method the most attractive, since it doesn't require loading any third-party classes. So unless I discover problems with it, that's the way I'm most likely to go.

The code

Here is the crucial code from each method.

ContainsKey

import java.util.HashMap;
import java.util.Map;
...
Map<String, Integer> freq = new HashMap<String, Integer>();
...
int count = freq.containsKey(word) ? freq.get(word) : 0;
freq.put(word, count + 1);

TestForNull

import java.util.HashMap;
import java.util.Map;
...
Map<String, Integer> freq = new HashMap<String, Integer>();
...
Integer count = freq.get(word);
if (count == null) {
freq.put(word, 1);
}
else {
freq.put(word, count + 1);
}

AtomicLong

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
...
final ConcurrentMap<String, AtomicLong> map =
new ConcurrentHashMap<String, AtomicLong>();
...
map.putIfAbsent(word, new AtomicLong(0));
map.get(word).incrementAndGet();

Trove

import gnu.trove.TObjectIntHashMap;
...
TObjectIntHashMap<String> freq = new TObjectIntHashMap<String>();
...
freq.adjustOrPutValue(word, 1, 1);

MutableInt

import java.util.HashMap;
import java.util.Map;
...
class MutableInt {
int value = 1; // note that we start at 1 since we're counting
public void increment () { ++value; }
public int get () { return value; }
}
...
Map<String, MutableInt> freq = new HashMap<String, MutableInt>();
...
MutableInt count = freq.get(word);
if (count == null) {
freq.put(word, new MutableInt());
}
else {
count.increment();
}

Increment an Integer within a HashMap

Do I have to return the object and then put a new one in ?

As long as you use the Integer wrapper class yes, because it's immutable. You could use a mutable wrapper class instead, even one that has an increment() method. However, you then lose the ability to use autoboxing and autounboxing on the values.

How to increment value of a given key with only one map lookup?

There is no way to increment the value with only one get and without a put. You always need to do a put afterwards, because Int is immutable. The inc() method (++ is a shortcut for that) returns a new value that need to be stored in the map. It does not change the stored value.

You can use

inventory.computeIfPresent("apples") { _, v -> v + 1 }

To write it in one line, but if you look into the implementation it will also do a get() then compute the new value and do a put() afterwards. So you can save a line of code you have to write, but you will not save CPU cycles on execution time.

How to increment a value, given a key in a java hashmap?

1. First you could directly use a Map<Attribute , Integer> and put like this :



mAttributesMap.put(Attribute.ONE, 5);
mAttributesMap.put(Attribute.TWO, 5);

2. To increment the value of a key, you can do as follow, get the value, and put it again with +1, because the key already exists it will just replace the existing one (keys in a map are unique) :

public void incrementValueFromKey(Attribute key){
mAttributesMap.computeIfPresent(key, (k, v) -> v + 1);
}

3. To have a more generic solution you could do :

public void incrementValueFromKey(Attribute key, int increment){
mAttributesMap.computeIfPresent(key, (k, v) -> v + increment);
}

How to increment value for a specific key in a Map in java?

You could use compute (Java 8+):

m.compute(key, (k, v) -> v + 1);

Find a String key associated with the Max Integer value in a MapString, Integer

You can do it like this. Stream entrySet of the map and then get the maximum entry by using maxBy on the value of the entry. Since maxBy returns an Optional, you can then use map to get the value. Return an informatory message if no value exists.

 String result  = wordFreq.entrySet().stream()
.collect(Collectors.maxBy(Entry.comparingByValue()))
.map(Entry::getKey).orElse("No Value Found");

And as was commented on you could also do this. I tend to use the first one out of habit.

String result = wordFreq.entrySet().stream()         
.max(Entry.comparingByValue())
.map(Entry::getKey).orElse("No Value Found");

You could also use other ways to optimize this but since you constructed a map I figured that is how you wanted to do it.
If you need a map of words, you may want to construct it outside of the method. Then just pass the map to the method instead of an array of sentences. That way you can use the map for other things.

Since you haven't yet had a chance to respond to my question, I will offer a solution regarding ties for the maximum occurrence of a word. Similar as before except first you return the maximum value found. The you can stream the entrySet, filtering for the entries that have the maximum value. Then just collect into a list.


int max = wordFreq.entrySet().stream()
.max(Entry.comparingByValue()).map(Entry::getValue)
.orElse(0);

List<String> wordFreq.entrySet().stream()
.filter(e -> e.getValue() == max).map(Entry::getKey)
.toList();

}

Lastly, you use stream constructs to create the initial frequency map. I am using \\s+ as the regex as there could be more than one space. The splitAsStream will apply the pattern and the toMap is similar to a merge in functionality.

Map<String, Integer> wordFreq = Arrays.stream(sentences)
.flatMap(s -> Pattern.compile("\\s+").splitAsStream(s))
.collect(Collectors.toMap(x -> x, x -> 1,
Integer::sum));



Related Topics



Leave a reply



Submit