It would appear at first glance from your example code that HashMap
's output ordering of a given set of String
s will always be the same depending on input order because:
- HashMap uses
hashCode
- hashcode on a string is consistent for any given String †
However, the Java HashMap
in it's current form may change the ordering as more elements are inserted (changing the hash table size), or for different insertion order of the same String set. For example:
for (int i = 1; i < 15; i++) {
//for (int i = 14; i > 0; i--) {
map1.put(String.format("%04d", i), "");
System.out.println(String.format("%04d:", i) + map1);
}
Running that forwards and backwards yields different iteration ordering after 14 insertions. Also between inserting "0013" and "0014" (running forwards) the last few elements are the same but the iteration order is changed:
0013:{... 0012=, 0003=, 0011=, 0002=, 0010=, 0009=, 0008=}
0014:{... 0003=, 0012=, 0002=, 0011=, 0009=, 0008=, 0010=}
This may look random, but run it again and it will happen in exactly the same way. Therefore this particular implementation is unpredictable in it's ordering as elements are inserted, but deterministic given the same starting and input conditions. I emphasise implementation as, in J7 (u6+), you can change this behaviour for various hash-based collections using java -Djdk.map.althashing.threshold=<threshold>
such that, across different JVM instances on the same machine, this behaviour becomes unpredictable.
Consistent hash-based map ordering
LinkedHashMap
will maintain iteration order (normally insertion-order). If you want to play around with the differences between the two, you can see the results more clearly with non value-based hashCode
s. You could wrap the String
as so:
class StrCont {
private String s;
public StrCont (String s) { this.s = s; }
public String toString() { return this.s; }
// uses the Object.hashCode implementation
}
The StrCont
class uses the default hashCode
from Object
. Therefore it is (generally) a hexString of the memory location for the object; the wrapped String
becomes irrelevant to the hash. Using that as your key:
map1.put( new StrCont("E._AUTO"), "20");
map1.put( new StrCont("E._ITERATIVE"), "20");
map1.put( new StrCont("E._ANDREW"), "20");
// need only 5/6 more than this to highlight the differences
Repeating this more than once produces new object references with the same String
"value", but totally different hashCode
s. Ordering is completely destroyed for the HashMap
, and maintained for the LinkedHashMap
.
TLDR: Value-based hashCode
s (such as those from String
) in your current JRE's HashMap
implementation are a distracting case where, because of your chosen implementation's (also internal state-based) determinism, you may start to think all HashMap
s give a consistent ordering based on the hashCode
.
But if you depend on consistent iteration ordering you need to use an ordered hash map such as LinkedHashMap
.
† While this is true (in J7 only) from J7u6 there is a hash32 function which maps may use if they have been switched to use the alternative hashing method with java -Djdk.map.althashing.threshold=<minEntries>
. This can therefore produce different ordering for the same String key input sequences even between restarts of a given JVM on the same machine.