Java 为什么HashMap有时会按自然顺序打印

Java 为什么HashMap有时会按自然顺序打印,java,hashmap,Java,Hashmap,我在介绍Java中的哈希映射时发现,哈希映射是无序和未排序的。因此,在使用System.out.println(HM)打印时,我们应该以键的任意顺序获得映射。例如,下面的代码 HashMap<Integer,String> HM = new HashMap<>(); HM.put(16,"hello16"); HM.put(6, "hello6"); HM.put(1, "hello1"); 我问了一位朋友,他说这与HashMap的初始容量(=16)有关,但他无法解释清

我在介绍Java中的哈希映射时发现,哈希映射是无序和未排序的。因此,在使用
System.out.println(HM)
打印时,我们应该以键的任意顺序获得映射。例如,下面的代码

HashMap<Integer,String> HM = new HashMap<>();
HM.put(16,"hello16");
HM.put(6, "hello6");
HM.put(1, "hello1");

我问了一位朋友,他说这与HashMap的初始容量(=16)有关,但他无法解释清楚。任何人都可以用这个特定的例子来解释输出中的这种差异。

HashMap使用键的hashCode将映射项抛出到桶的索引数组中。检索顺序或多或少是任意的。由于相同长度的字符串(仅在最后一个字符中不同)通常具有等于X+最后一个字符的哈希代码,因此它们之间存在顺序

实现类TreeMap的另一个映射是SortedMap,它按键的顺序保存条目


另一个映射实现是LinkedHashMap,其中的顺序是将条目放入映射中的顺序。

整数的
hashCode
是值本身。您的
HashMap
有16个bucket,这意味着分配值的bucket是
键%16
,它是一个从0到15的数字

如果您的钥匙在0到15之间,则桶号就是钥匙。只有当你使用键
>15
<0
时,事情才会变得一团糟

打印
HashMap
时,条目按存储桶顺序显示。也就是说,如果存在存储桶0中的密钥,则首先打印该密钥;然后将钥匙插入铲斗1,依此类推。在
HashMap
中,所有键都在0到15之间,这与键顺序完全相同

该代码打印
{16=hello16,1=hello1,6=hello6}
,这是键的随机顺序

顺序可能是任意的,但它是确定的。订单基于三个因素:

  • 密钥的哈希代码的值
  • 哈希表中的桶数,以及
  • 插入键的顺序(用于确定具有相同哈希键的项的顺序)
按哈希桶枚举哈希表条目。由于散列桶的数量低于可能的散列码的范围,因此散列码的值会间接转换为散列桶的索引。在散列键的顶部应用补充的散列函数,然后使用按位
&
获取实际索引:

static int hash(int h) {
    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
...
static int indexFor(int h, int length) {
    return h & (length-1);
}
您可以从代码的
索引中看到,
长度
应该是二的幂(这就是为什么他们可以使用
&(length-1)
而不是
%length
表达式)。在您的例子中,这很重要,因为整数的哈希代码匹配相应整数的值。假设容量为16个铲斗,16个将转换为铲斗零,而15个将转换为铲斗15(铲斗从零开始编号)


这就是为什么在您的示例中,15的值从前面移到后面。

发生这种情况的原因并不重要,因为您不应该依赖它。在Java 9中,由于这个原因,
Set#和
Map#的插入顺序是随机的。@RomanPuchkovskiy OP在问题中说“…当打印使用系统.out.println(HM)…”,所以我们应该以键的随机顺序获得映射”-不。HashMap不保证随机顺序。它完全没有承诺订单是什么。今天它可能是由内部桶订购的。下一个版本,它可以像Go一样随机化。在其他人的机器上,它可能像新的Python dict实现一样按插入顺序排列。“似乎不太可能是偶然的”:你有六分之一的机会有三个值按自然顺序排列,而你花了一次机会却没有得到它。你不应该依赖于顺序或桶大小。这是一个内部实现细节,可能会随着时间的推移而改变(从一个版本到另一个版本)。@tobain您完全正确。但问题是,为什么OP的当前代码会出现这种情况。
static int hash(int h) {
    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
...
static int indexFor(int h, int length) {
    return h & (length-1);
}