I've tried to build my own Map to increase the performance for a special environment, and I realized something pretty interesting: Creating a new Hashmap<Integer,String>(2000)
is faster than new Object[2000]
- no matter in which order I execute these commands. That's pretty confusing to me, esp. because the Hashmap constructor contains a table = new Entry[capacity]
, according to this. Is there something wrong with my testbench?
public static void test(int amm){ //amm=1_000_000 Map<Integer,String> m1 = null; Object[] arr = null; long time = System.nanoTime(); for(int i = 0; i < amm; i++){ m1 = new HashMap<Integer, String>(2000); } System.out.println("m1: " + (System.nanoTime() - time)); //m1: 70_455_065 time = System.nanoTime(); for(int i = 0; i < amm; i++){ arr = new Object[2000]; } System.out.println("arr: " + (System.nanoTime() - time)); //arr: 1_322_473_803 }
I'd love to see the results of testing on another computer. I've got no clue why creating a HashMap
is 10 times faster than creating a Object[]
.
The reason that HashMap is faster than HashSet is that the HashMap uses the unique keys to access the values. It stores each value with a corresponding key and we can retrieve these values faster using keys during iteration. While HashSet is completely based on objects and therefore retrieval of values is slower.
HashMap, being a hashtable-based implementation, internally uses an array-based data structure to organize its elements according to the hash function. HashMap provides expected constant-time performance O(1) for most operations like add(), remove() and contains(). Therefore, it's significantly faster than a TreeMap.
HashMap is faster than HashSet because the values are associated to a unique key. In HashSet , member object is used for calculating hashcode value which can be same for two objects so equals() method is used to check for equality.
For each element, HashMap computes the hash code and puts the element in the bucket associated with that hash code. Because non-equal objects can have the same hash codes (a phenomenon called hash code collision), buckets can grow in size. The bucket is actually a simple linked list.
If you look at the implementation of HashMap
, the constructor looks like:
public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; threshold = initialCapacity; init(); }
And init()
looks like:
/** * Initialization hook for subclasses. This method is called * in all constructors and pseudo-constructors (clone, readObject) * after HashMap has been initialized but before any entries have * been inserted. (In the absence of this method, readObject would * require explicit knowledge of subclasses.) */ void init() { }
So initialCapacity
doesn't actually get used to create an array. Where does it get used? Look at the put()
method.
public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } // hidden }
When doing a put, the array is actually created. I didn't show inflateTable()
but it does some math and initializes the array.
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