What values should I pass to create an efficient HashMap
/ HashMap
based structures for N items?
In an ArrayList
, the efficient number is N (N already assumes future grow). What should be the parameters for a HashMap
? ((int)(N * 0.75d), 0.75d)? More? Less? What is the effect of changing the load factor?
In the guava libraries from Google there is a function that creates a HashMap optimized for a expected number of items: newHashMapWithExpectedSize
from the docs:
I ran some unit tests to see if these answers were correct and it turned out that using:
as the initial capacity gives what you want for either a
HashMap
or aHashtable
. By "what you want" I mean that addingrequiredCapacity
elements to the map won't cause the array which it's wrapping to resize and the array won't be larger than required. Since the default load capacity is 0.75, initializing a HashMap like so works:Since a HashSet is effectively just a wrapper for a HashMap, the same logic also applies there, i.e. you can construct a HashSet efficiently like this:
@Yuval Adam's answer is correct for all cases except where
(requiredCapacity / 0.75)
is a power of 2, in which case it allocates too much memory.@NotEdible's answer uses too much memory in many cases, as the HashMap's constructor itself deals with the issues that it want the maps array to have a size which is a power of 2.
Erm, no it doesn't, unless I misunderstand what you're saying here. When you pass an integer into the Arraylist constructor, it will create an underlying array of exactly that size. If it turns out you need even a single extra element, the ArrayList will need to resize the underlying array when you next call add(), causing this call to take a lot longer than it usually would.
If on the other hand you're talking about your value of N taking into account growth - then yes, if you can guarantee the value will never go above this then calling such an Arraylist constructor is appropriate. And in this case, as pointed out by Hank, the analogous constructor for a map would be N and 1.0f. This should perform reasonably even if you do happen to exceed N (though if you expect this to occur on a regular basis, you may wish to pass in a larger number for the initial size).
The load factor, in case you weren't aware, is the point at which the map will have its capacity increased, as a fraction of the total capacity.
Edit: Yuval is probably right that it's a better idea to leave the load factor around 0.75 for a general purpose map. A load factor of 1.0 would perform brilliantly if your keys had sequential hashcodes (such as sequential integer keys), but for anything else you will likely run into collisions with the hash buckets, meaning that lookups take longer for some elements. Creating more buckets than is strictly necessary will reduce this chance of collision, meaning there's more chance of elements being in their own buckets and thus being retrievable in the shortest amount of time. As the docs say, this is a time vs space tradeoff. If either is particularly important to you (as shown by a profiler rather than prematurely optimising!) you can emphasize that; otherwise, stick with the default.
It's safe in most cases of
List
andMap
initialization to make theList
orMap
with the following size params.this follows the .75 rule as well as saves a little overhead over the
* 2
operation described above.It's also notable that having a HashMap on the small side makes hash collisions more likely, which can slow down lookup. Hence, if you really worry about the speed of the map, and less about its size, it might be worth making it a bit too large for the data it needs to hold. Since memory is cheap, I typically initialise HashMaps for a known number of items with
Feel free to disagree, in fact I'd quite like to have this idea verified or thrown out.
Referring to HashMap source code will help.
If the number of entries reaches threshold(capacity * load factor), rehashing is done automatically. That means too small load factor can incur frequent rehashing as entries grow.