Recently I was wondering if there are alternatives to the Java Collections Implementations and if they have a better performance than the java.util package. Especially the performance of HashMaps was of interest. At the company I work at we have keep lots of business data entries in memory and those entries are stored in HashMaps.
In several Articles I read that other implementaions are faster than java.util.HashMap. So I was conducting a performance check.
Alternatives
The alternatives to java.util that I considered were:
<dependency> <groupId>net.sf.trove4j</groupId> <artifactId>trove4j</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>15.0</version> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency> <dependency> <groupId>javolution</groupId> <artifactId>javolution</artifactId> <version>5.5.1</version> </dependency>
Result
java.util.HashMap is the fastest implementation to date! I checked if different constructors have an impact to the performance of the individual HashMap. It took on average 45ms to get all Objects out of a HashMap with 1.000.000 items, and it took on average 80ms to put 1.000.00 items into the HashMap. Here is the data:
| Implementation | get() in ms | put() in ms |
| java.util.HashMap(1_000_000,1f) | 60 | 1728 |
| java.util.HashMap(1_000_000,10f) | 43 | 104 |
| java.util.HashMap(1_000_000,0.3f) | 42 | 78 |
| java.util.HashMap(1_000_000,0.1f) | 42 | 112 |
| java.util.HashMap() | 40 | 87 |
| java.util.HashMap(1_000_000) | 41 | 76 |
| gnu.trove.map.hash.THashMap() | 77 | 250 |
| gnu.trove.map.hash.THashMap(1_000_000) | 70 | 74 |
| gnu.trove.map.hash.THashMap(1_000_000,1f) | 161 | 155 |
| javolution.util.FastMap() | 119 | 272 |
| javolution.util.FastMap(1_000_000) | 100 | 116 |
| org.apache.commons.collections.FastHashMap() | 65 | 125 |
| org.apache.commons.collections.FastHashMap(1_000_000) | 64 | 87 |
| java.util.TreeMap() | 269 | 305 |
| java.util.HashMap() | 48 | 89 |
| org.apache.commons.collections.FastTreeMap() | 269 | 331 |
The performance of the java.util.HashMap(1_000_000,1f) testrun seems to be impacted by the startup and optimizing of the code by the JVM. I think this value is not accurate. A reordering of the testruns supports this thesis.
Lets take a look at the results in a chart:
Conclusion
The java.util.HashMap implementaion is still faster than other implementations. At this point it is not worthwhile to use other libraries in most cases.
In Java 7 there seem to be major Collection improvements as this article noted that in Java 6 the Trove library was faster than in java.util.HashMap. Using constructors with a predetermined initial map size improves inserting into the map, but not by a large extend.
Links
- http://javolution.org/target/site/apidocs/javolution/util/FastMap.html
- http://karussell.wordpress.com/2009/09/03/java-collections-are-there-alternatives/
- http://stackoverflow.com/questions/3972127/hashmap-alternatives-for-memory-efficient-data-storage
- http://stackoverflow.com/questions/3973431/performance-of-collection-class-in-java
- http://karussell.wordpress.com/2009/09/03/java-collections-are-there-alternatives/
Source
Environment:
java version “1.7.0_40”
Java(TM) SE Runtime Environment (build 1.7.0_40-b43)
Java HotSpot(TM) 64-Bit Server VM (build 24.0-b56, mixed mode)
The sourcecode of the performance Test:
package info.klewitz.hashmap;
import com.google.common.collect.Maps;
import gnu.trove.map.hash.THashMap;
import javolution.util.FastMap;
import org.apache.commons.collections.FastHashMap;
import org.apache.commons.collections.FastTreeMap;
import org.springframework.util.StopWatch;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class HashMapSpeedTest {
public static final int SIZE = 1_000_000;
private Set<String> objects;
private StopWatch stopWatch;
@BeforeClass
public void setUp(){
System.out.println("creating " + SIZE + " objects");
System.out.println("Implementation;get();put()");
objects = getObjects();
stopWatch = new StopWatch();
}
@DataProvider
public Object[][] mapProvider(){
return new Object[][]{
{ new HashMap<String,Object>(SIZE,1f),"SIZE,1f" },
{ new HashMap<String,Object>(SIZE,10f),"SIZE,10f" },
{ new HashMap<String,Object>(SIZE,0.3f),"SIZE,0.3f" },
{ new HashMap<String,Object>(SIZE,0.1f),"SIZE,0.1f" },
{ new HashMap<String,Object>(),"" },
{ new HashMap<String,Object>(SIZE),"SIZE" },
{ new THashMap<String,Object>(),""},
{ new THashMap<String,Object>(SIZE), "SIZE"},
{ new THashMap<String,Object>(SIZE,1f), "SIZE,1f"},
{ new FastMap<String,Object>(), ""},
{ new FastMap<String,Object>(SIZE), "SIZE"},
{ new FastHashMap(),""},
{ new FastHashMap(SIZE),"SIZE"},
{ new TreeMap<String,Object>(),""},
{Maps.newHashMap(),""},
{ new FastTreeMap(),""},
};
}
@Test(dataProvider = "mapProvider",singleThreaded = true)
public void test(Map<String,Object> map,String typeExtension) {
String type = map.getClass().getName() + "("+typeExtension+")";
stopWatch.start(type+"put()");
for(String o:objects){
map.put(o,o);
}
stopWatch.stop();
long putTime = stopWatch.getLastTaskTimeMillis();
stopWatch.start(type+"get()");
for(String o:objects){
map.get(o);
}
stopWatch.stop();
long getTime = stopWatch.getLastTaskTimeMillis();
System.out.println(type + ";" + getTime + ";" + putTime);
map.clear();
System.gc();
}
@AfterClass
public void tearDown() throws Exception {
System.out.println(stopWatch.prettyPrint());
}
private Set<String> getObjects() {
Set<String> objects = new HashSet<>();
for(int i=0;i< SIZE;i++){
objects.add("" + i);
}
return objects;
}
}