0

我正在使用 Java 中的 HashMap 类。我的理解是哈希表的容量是桶数的 2 次方(容量 16 表示四个桶)。调用 put(key,value) 时,key.hashCode() 输出一个整数,这个新添加的 (key,value) 对基于 key.hashCode()% 个桶放置。但以下是 HashMap.class 中的实际实现

 static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

从上面的代码中,我无法弄清楚如何将 key.hashCode() 值拟合到存储桶中。

4

2 回答 2

2

这可能会有所帮助。如果我们要添加 10 个带有从 1 到 10 的浮动键的项目。

        Map<Float, String> map = new HashMap<>();
        int numOfBuckets = 64; // HashMap will have 64 bins after inserting 10 items

        String format = "|%1$-5s|%2$-35s|%3$-25s|%4$-35s|%5$-25s|%6$-25s|\n";
        System.out.format(format, "i", "raw binary", "right-shifted 16 bits", "rehashed", "bucket before rehashed",
                "bucket after rehashed");

        for (int i = 1; i <= 10; i++) {
            float key = i;
            int rawInt = Float.floatToRawIntBits(key);
            String binaryString = Long.toBinaryString(rawInt);
            String shifted16BitsString = Long.toBinaryString(rawInt >>> 16);

            int rehashed = rawInt ^ rawInt >>> 16;
            String rehashedString = Long.toBinaryString(rehashed);

            // HashMap calculates bin index with (n - 1) & hash
            String bucketBeforeRehashed = Long.toString((numOfBuckets - 1) & Objects.hashCode(key));
            String bucketAfterRehashed = Long.toString((numOfBuckets - 1) & rehashed);

            System.out.format(format, i, binaryString, shifted16BitsString, rehashedString,
                    bucketBeforeRehashed, bucketAfterRehashed);
            map.put(key, Integer.toString(i));
        }

产生:

|i    |raw binary                         |right-shifted 16 bits    |rehashed                           |bucket before rehashed   |bucket after rehashed    |
|1    |111111100000000000000000000000     |11111110000000           |111111100000000011111110000000     |0                        |0                        |
|2    |1000000000000000000000000000000    |100000000000000          |1000000000000000100000000000000    |0                        |0                        |
|3    |1000000010000000000000000000000    |100000001000000          |1000000010000000100000001000000    |0                        |0                        |
|4    |1000000100000000000000000000000    |100000010000000          |1000000100000000100000010000000    |0                        |0                        |
|5    |1000000101000000000000000000000    |100000010100000          |1000000101000000100000010100000    |0                        |32                       |
|6    |1000000110000000000000000000000    |100000011000000          |1000000110000000100000011000000    |0                        |0                        |
|7    |1000000111000000000000000000000    |100000011100000          |1000000111000000100000011100000    |0                        |32                       |
|8    |1000001000000000000000000000000    |100000100000000          |1000001000000000100000100000000    |0                        |0                        |
|9    |1000001000100000000000000000000    |100000100010000          |1000001000100000100000100010000    |0                        |16                       |
|10   |1000001001000000000000000000000    |100000100100000          |1000001001000000100000100100000    |0                        |32                       |

我们可以从输出中发现,key 的低位都是 0,这导致所有 item 都被分配到同一个 bin。但是在执行右移和异或之后分布变得更好。我认为这是 HashMap 的 hash() 方法中源代码的注释中描述的情况。

于 2019-05-14T09:35:29.503 回答
1

该代码不“适合”散列码到桶,它“只是”传播散列码,使高位更重要。这里是该方法的javadoc。

计算 key.hashCode() 并将哈希的较高位传播(XOR)到较低位。由于该表使用二次幂掩码,因此仅在当前掩码之上位变化的散列集将始终发生冲突。(已知的例子是在小表中保存连续整数的 Float 键集。)因此,我们应用了一种变换,将高位的影响向下传播。在位扩展的速度、实用性和质量之间存在折衷。因为许多常见的散列集已经合理分布(所以不要从传播中受益),并且因为我们使用树来处理 bin 中的大量冲突,我们只是以最便宜的方式对一些移位的位进行异或,以减少系统损失,以及合并最高位的影响,否则由于表边界,这些最高位将永远不会用于索引计算。

对桶的实际拟合是在以下getNode(int, Object)方法中完成的:

first = tab[(n - 1) & hash]

其中hash是结果,hash(Object)n哈希表的大小。

于 2016-12-03T17:38:22.693 回答