【问题标题】:What property of the bit pattern is it that causes collisions?位模式的什么属性会导致冲突?
【发布时间】:2015-08-19 12:27:27
【问题描述】:

我正在阅读有关 Java 随机化哈希键的方法 here
显然,这个想法是确保低位是“随机的”以帮助分发,但我试图更多地理解这一点。
因此,如果我们有一个大小为 10 的表,那么数字 0、10、20、30、40 等都属于存储桶 0,数字 1、11、21、31 等属于存储桶 1 等(使用模 10)。 因此,更改位模式可以使它们进入不同的存储桶,而不是全部进入存储桶 0。
但我不清楚的是,是什么属性使低位影响这一点,我们需要将它们随机化。 所以我们有:

0000 0000 (0)  
0000 1010 (10)  
0001 0100 (20) 
0001 1110 (30)  
0010 1000 (40) 

低位位的规律性是什么使它们被放置到同一个槽中?
也许我对以下内容感到困惑?我的理解是低阶位的一些规律性会导致冲突,我们尝试随机化位来补偿

【问题讨论】:

    标签: java algorithm hash hashmap hash-collision


    【解决方案1】:

    一些散列函数在随机化低位方面做得非常糟糕。

    一个经典案例是使用硬件地址作为对象引用(C 中的“指针”)的哈希值,否则这将是一种合理的方法,可以廉价地获取对象 ID 的唯一编号。如果哈希表的存储桶计数是素数,这将正常工作,但对于存储桶数量始终为 2 的幂的哈希实现,所有哈希可被 8 整除的事实意味着大多数存储桶是空的。

    这是一个极端的情况,但是任何时候要散列的数据不是均匀分布的并且散列函数倾向于保留低位,你会发现桶分配中存在一些偏差。

    【讨论】:

    • 对此我不清楚:..but for hash implementations where the number of buckets is always a power of 2, the fact that all the hashes are divisible by 8 would mean that most buckets were empty. 8 是多少?地址大小?为什么会发生 2 的幂大小?能否请您详细说明一下?
    • @Jim: 8 是典型的硬件对齐的(一个例子):几乎所有对象的地址都可以被 8 整除,因为 CPU 可以在一次访问中读取八个对齐的字节(而如果对象被拆分在边界上,它将需要两次内存访问)。如果你将一个能被 8 整除的数以 2 的某个幂为模减少,你最终会得到一个能被 8 整除的值,因此每 8 个存储桶中有 7 个是未使用的。
    【解决方案2】:

    Java 的 HashMap 使用的哈希表大小是 2 的幂。如果您像往常一样使用余数/模运算作为压缩函数,您最终会将哈希码的最低位作为存储桶索引。如果哈希码恰好是 2 的倍数,则某些最低位将始终为零,并且您最终会使用可用存储桶的一小部分。

    具体例子:假设你有 32 个桶,哈希码是 8 的倍数。该表只使用了代码的 5 个最低有效位,其中 3 个始终为 0。因此只有 2 位确定桶,并且你只使用了 32 个桶中的 4 个:

    XXXXXX00000 -> bucket 0
    XXXXXX01000 -> bucket 8
    XXXXXX10000 -> bucket 16
    XXXXXX11000 -> bucket 24
    

    幸运的是,Java 中的情况并没有这么糟糕,因为 HashMap 不只使用哈希码的最低位:它会打乱这些位,因此不太容易意外产生不良场景。以下是 OpenJDK 的 HashMap 实现的摘录:

    /**
     * Applies a supplemental hash function to a given hashCode, which
     * defends against poor quality hash functions.  This is critical
     * because HashMap uses power-of-two length hash tables, that
     * otherwise encounter collisions for hashCodes that do not differ
     * in lower bits. Note: Null keys always map to hash 0, thus index 0.
     */
    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);
    }
    

    【讨论】:

    • 我不太明白这个说法Java's HashMap uses a hash table sizes that are powers of two, which means is that it basically takes the lowest bits of the hash code as the bucket index.你能详细说明一下吗?
    • 我已经扩展了一点。通常,您拥有的哈希码比存储桶多,因此您使用“压缩函数”将哈希码映射到存储桶。压缩函数的常见选择是计算代码除以桶数时的余数。如果桶数为 2^N,则结果为哈希码的最低 N 位。
    • 感谢您的更新。这是使用 2 的幂时出现的问题,对吗?所以素数大小会导致更好的分布,但它会导致问题增长到下一个素数大小,因为它更慢?
    • 素数大小确实存在这个问题,并且还需要使用余数运算来计算存储桶索引。这比使用 2 次方的按位 AND 慢。不过,C# 标准库中的哈希表使用素数大小。
    猜你喜欢
    • 1970-01-01
    • 2019-12-22
    • 2016-11-18
    • 1970-01-01
    • 1970-01-01
    • 2021-09-17
    • 2012-01-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多