【问题标题】:Why does a HashMap sometimes print in natural order [duplicate]为什么HashMap有时会以自然顺序打印[重复]
【发布时间】:2018-05-01 14:42:30
【问题描述】:

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

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

打印{16=hello16, 1=hello1, 6=hello6},这是一个明显随机的键顺序。但是当我用HM.put(15,"hello15"); 替换HM.put(16,"hello16"); 时,它会以键的自然顺序打印映射,is surprising and seems unlikely by chance:

{1=hello1, 6=hello6, 15=hello15}

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

【问题讨论】:

  • 为什么会发生这种情况并不重要,因为您不应该依赖它。在 Java 9 中,Set#ofMap#of 的插入顺序是随机的。
  • @RomanPuchkovskiy OP 在问题“...打印使用 System.out.println(HM) 时说。...
  • “所以我们应该以随机的键顺序获取映射” - 不。 HashMap 不承诺随机顺序。它根本不承诺订单将是什么。
  • 今天它可能会被内部存储桶订购。下一个版本,它可以像 Go 一样随机化。在别人的机器上,它可能像新的 Python dict 实现一样是按插入排序的。
  • “似乎不太可能是偶然的”:您有六分之一的机会 3 个值按自然顺序排列,而您花了一次机会却没有得到。

标签: java hashmap


【解决方案1】:

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

顺序可能看起来是任意的,但它是确定性的。顺序基于三个因素:

  • 键的哈希码值,
  • 哈希表中的桶数,以及
  • 插入键的顺序(用于决定具有相同哈希键的项目的顺序)

枚举哈希表条目通过哈希桶进行。由于哈希桶的数量低于可能的哈希码范围,因此哈希码的值间接转化为哈希桶的索引。 Java implementation 在散列键之上应用一个补充散列函数,然后按位使用&amp; 得到实际索引:

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);
}

您可以从indexFor 代码中看到length 应该是2 的幂(这就是他们可以使用&amp; (length-1) 而不是% length 表达式的原因)。在您的情况下,这很重要,因为整数的哈希码与相应整数的值匹配。假设有 16 个桶的容量,16 将转换为桶 0,而 15 将转换为桶 15(桶从零开始编号)。

这就是示例中 15 的值从前到后移动的原因。

【讨论】:

    【解决方案2】:

    IntegerhashCode 是值本身。你的HashMap有16个bucket,也就是说赋值给的bucket是key % 16,是0到15之间的一个数字。

    如果您的键在 0 到 15 的范围内,则桶号 键。只有当您使用密钥 &gt; 15&lt; 0 时,事情才会变得混乱。

    当您打印HashMap 时,条目按存储桶顺序显示。也就是说,桶 0 中的一个键首先打印,如果有的话;然后是桶 1 中的一个键,依此类推。对于您的HashMap,所有键都在 0 到 15 之间,这与键顺序完全相同。

    【讨论】:

    • 不应依赖该顺序或存储桶大小。这是一个内部实现细节,可能会随着时间而改变(从一个版本到另一个版本)。
    • @tobain 你完全正确。但问题是,为什么 OP 的当前代码会发生这种情况。
    【解决方案3】:

    HashMap 使用键的 hashCode 将映射条目放入索引的存储桶数组中。检索顺序或多或少是任意的。由于相同长度的字符串,只是最后一个字符不同,通常哈希码等于 X + 最后一个字符,它们之间有一个顺序。

    另一个 Map 实现类 TreeMap 是一个 SortedMap,并按照键的顺序保持条目。

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

    【讨论】:

      猜你喜欢
      • 2020-05-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-08
      • 1970-01-01
      • 2018-05-15
      • 1970-01-01
      相关资源
      最近更新 更多