I agree with:
hashCode()
implementation could result to multiple collisions, which means that in the worst case every object goes to the same bucket, thus O(N) if each bucket is backed by a List
.HashMap
dynamically replaces the Nodes (linked list) used in each bucket with TreeNodes (red-black tree when a list gets bigger than 8 elements) resulting to a worst performance of O(logN).But, this is not the full truth if we want to be 100% precise. The implementation of hashCode()
and the type of key Object
(immutable/cached or being a Collection) might also affect real time complexity in strict terms.
Let's assume the following three cases:
HashMap<Integer, V>
HashMap<String, V>
HashMap<List<E>, V>
Do they have the same complexity? Well, the amortised complexity of the 1st one is, as expected, O(1). But, for the rest, we also need to compute hashCode()
of the lookup element, which means we might have to traverse arrays and lists in our algorithm.
Lets assume that the size of all of the above arrays/lists is k.
Then, HashMap<String, V>
and HashMap<List<E>, V>
will have O(k) amortised complexity and similarly, O(k + logN) worst case in Java8.
*Note that using a String
key is a more complex case, because it is immutable and Java caches the result of hashCode()
in a private variable hash
, so it's only computed once.
/** Cache the hash code for the string */
private int hash; // Default to 0
But, the above is also having its own worst case, because Java's String.hashCode()
implementation is checking if hash == 0
before computing hashCode
. But hey, there are non-empty Strings that output a hashcode
of zero, such as "f5a5a608", see here, in which case memoization might not be helpful.