为什么 Java *排序* HashMap 条目,即使它不应该?

Why does Java *sort* HashMap entries even though it shouldn't?

Java 中的 HashMap 不应排序,并且不符合 gua运行 发球顺序。它的顺序可以在应用程序的整个生命周期中更改,如果你想要一个排序的地图,你应该使用 LinkedHashMap,或者更好的是 TreeMap.

知道这个,经历过这个,并且official documentation证实了这一点。

但是,我刚刚写了一些代码,不会保持HashMap未排序。起初,我以为这是偶然的巧合,但我 运行 代码多次 显示相同的 输出。

Map<String, Double> map = new HashMap<>();

map.put("A", 99.5);
map.put("B", 67.4);
map.put("C", 67.4);
map.put("D", 67.3);

System.out.println("Unsorted map: " + map);

这导致:

Unsorted map: {A=99.5, B=67.4, C=67.4, D=67.3}

我假设 String 键以某种方式按字典顺序排序,因为它们遵循 ABCD,但是,这是另一个示例,其中排序的 String 键不一定按词典值排序:

Map<String, Integer> unsortedMap = new HashMap();

unsortedMap.put("David", 21);
unsortedMap.put("Scott", 34);
unsortedMap.put("Marcus", 31);
unsortedMap.put("Vladimir", 24);

unsortedMap.entrySet().forEach(System.out::println);

这一个结果是:

Marcus=31
David=21
Vladimir=24
Scott=34

Marcus 在字典序上大于 David,但 David 在字典序上 小于 Vladimir.

我假设这部分源代码具体负责:

static int tieBreakOrder(Object a, Object b) {
    int d;
    if (a == null || b == null ||
        (d = a.getClass().getName().
         compareTo(b.getClass().getName())) == 0)
        d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
             -1 : 1);
    return d;
}

注:我是运行Java15,虽然回滚到旧版本,比如Java8没有'根本不改变行为。

假设 String 键已排序是正确的 - 但它不是按字典顺序排序而是基于键的散列。
方法 java.util.HashMap#put 使用方法 java.util.HashMap#hash 计算键的哈希值 - 在我们的例子中是 String
java.util.HashMap#hash 对于字母 A - 65、字母 B - 66、字母 C - 67 等单字母对象的结果(检查 A-Za-z,不能说明其他值)。 David 的 java.util.HashMap#hash 结果 - 65805752.

然后使用计算的哈希值来确定方法java.util.HashMap#putVal中基础数组java.util.HashMap#table中的桶的索引。代码的确切部分:

p = tab[i = (n - 1) & hash]

它根据哈希值有效地获取适当的数组索引。

所以,是的 - 无法保证条目的顺序 - 但可以通过了解底层节点数组的确切大小和了解要放入映射中的键的哈希码来操纵它