Clarification before I answer your question:
From your question it sounds like the first 8 nodes are always managed by a linkedlist structure and the ones after the 8th node are managed by a tree structure.
But this is how it actually works after it was changed in Java 8:
- If the bucket contains a small number of nodes (< 8), it uses a linkedlist for all of this bucket's nodes.
- If the bucket contains a large number of nodes (=> 8), it dynamically replaces it with an ad-hoc implementation of tree map, for all of this bucket's nodes.
Why does it work like this?
You have a more thorough explanaition here, but basically:
Well, previously entries with conflicting keys were simply appended to linked list, which later had to be traversed. Now HashMap promotes list into binary tree, using hash code as a branching variable. If two hashes are different but ended up in the same bucket, one is considered bigger and goes to the right. If hashes are equal (as in our case), HashMap hopes that the keys are Comparable, so that it can establish some order. This is not a requirement of HashMap keys, but apparently a good practice. If keys are not comparable, don't expect any performance improvements in case of heavy hash collisions.
In the case of high hash collisions, this implementation will improve worst-case performance from O(n) to O(log n).
For reference, this implementation change in HashMap was introdiced by JEP 180
Why aren't they always handled as tree?
They could have, I guess. I see 2 reasons, that combined could answer this:
- There is a space penalty for using a tree, as explained in this answer.
- Switching to a tree will be triggered only in rare cases where there are many collisions. A bucket shouldn't usually contain 8 or more nodes. If it does, this means that there has been a very high number of collisions (due to a bad key hashcode implementation or really bad luck). In most cases, where there are low collisions, a simply linked list will suffice since time complexity will tend to be constant 0(1).