2

I read about HashMap and it says insertion order is not maintained. I have executed below code and for 10,000 times response is returned in same order.

Also in key, I am just changing prefix from E to M. Can someone please help in explaining this behavior?

for ( int i = 0; i < 10000; i++ ) {
            Map<String, String> map1 = new HashMap<String, String>();
            map1.put( "E._AUTO", "20");
            map1.put( "E._ITERATIVE", "20");
            map1.put( "E._ANDREW", "20");
            System.out.println(map1);

            Map<String, String> map2 = new HashMap<String, String>();
            map2.put( "M._AUTO", "20");
            map2.put( "M._ITERATIVE", "20");
            map2.put( "M._ANDREW", "20");
            System.out.println(map2);
        }

Output:

{E._ANDREW=20, E._ITERATIVE=20, E._AUTO=20}
{M._ITERATIVE=20, M._AUTO=20, M._ANDREW=20}
Nikhil Joshi
  • 817
  • 2
  • 12
  • 34
  • 1
    In case, just in case you need to rely on order, read about `LinkedHashMap` – Sid Jan 20 '15 at 14:24
  • possible duplicate of [Order of values retrieved from a HashMap](http://stackoverflow.com/questions/2144776/order-of-values-retrieved-from-a-hashmap) – Andy Brown Jan 20 '15 at 14:26
  • 1
    They all map to the same bucket in the HashMap so you get a linked list of the entries in reverse order of adding them. Note: if you change the keys, or the capacity, or use Java 8 the behaviour is different. – Peter Lawrey Jan 20 '15 at 14:53

3 Answers3

11

I have executed below code and for 10,000 times response is returned in same ordered.

That's just what happens to occur with the version you're using and the values you're inserting. There's no guarantee it will continue to occur, or that insertion order is maintained if you add different values, or if you remove items then add others.

Basically, it's not saying that it definitely won't be in a particular order - it's saying that you absolutely should not rely on it being in that order.

Also note that if you expected insertion order to be maintained, your examples already demonstrate that it's not. The output shows items not being presented in insertion order.

LinkedHashMap will maintain insertion order, by maintaining a linked list of entries alongside the hash map internally. (An exception here is that there's a constructor which allows you to specify that items will be presented in access order rather than insertion order, which is usually used when you want this as the basis of a cache.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
3

The natural implementation of HashMap loses information on what order it's elements got inserted into the Map. That's it really, this means that you lose this information if not tracking it explicitly. This doesn't say anything about two identically filled HashMap's contents and order of contents. When an element is inserted, the algorithm puts it in the nearest possible place in the Map (these are hidden from you). If you put the same objects in the same order into two maps, it's only logical they will look similar. You still lose information about the order in which you put your elements in the Map.

Attila Neparáczki
  • 466
  • 1
  • 6
  • 14
2

It would appear at first glance from your example code that HashMap's output ordering of a given set of Strings will always be the same depending on input order because:

  1. HashMap uses hashCode
  2. 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 hashCodes. 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 hashCodes. Ordering is completely destroyed for the HashMap, and maintained for the LinkedHashMap.

TLDR: Value-based hashCodes (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 HashMaps 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.

Community
  • 1
  • 1
Andy Brown
  • 18,961
  • 3
  • 52
  • 62
  • 1
    That logic is false. It would be entirely feasible for each `HashMap` to have some random offset which affected ordering, so that each map you created would potentially have a different order - it would still obey the contract, and would still follow the rules you've specified. – Jon Skeet Jan 20 '15 at 14:24
  • 1
    @JonSkeet Thank you, I have edited my answer to correct and make my original point somewhat more clearly. – Andy Brown Jan 20 '15 at 15:02
  • 1
    I don't think it's a good idea to talk about string keys being a special case. It's still version-specific - a different version of the JRE could easily give you a different result. Better to just give advice not to rely on ordering at all. – Jon Skeet Jan 20 '15 at 15:04