【问题标题】:Why java hashcode implementation 31 * x + y is better than x + y?为什么java hashcode实现31 * x + y比x + y好?
【发布时间】:2020-08-02 15:59:19
【问题描述】:

我对关于哪个哈希码实现更好的 Java 面试问题感到困惑。我们有一个类 Point {int x, y; }。为什么这个类的 hashcode 31 * x + y 的实现比 x + y 好?正确的答案是“乘数使哈希码值依赖于处理字段的顺序,最终产生更好的哈希函数”。但是我不明白为什么处理顺序是这里的重点,因为整个表达式 31 * x + y 在我执行 point1.equals(point2); 时正在计算并且无论它发生的顺序是什么。我错了吗?

【问题讨论】:

  • 如果您碰巧使用 Point 作为 map/hashtable 中的键,则使用 hashCode() {return x + y;} 会为点 (1,2) 和点 (2,1) 等产生冗余哈希冲突等。跨度>
  • 我认为碰撞的数量将保持不变。使用hashCode() {return 31 * x + y;},对 (0,31) 和 (1,0) 也会发生冲突。
  • @Amongalen 发生冲突总是可能,但在实际使用中,值 0 和 1 比 31 更频繁地被发现。
  • @Amongalen 没有完美的哈希,并且都有冲突。但是在通常情况下,使用的函数可能会导致更少(或更多)的碰撞。 x+y 会通过对称性引入碰撞(几何学中非常常见的属性)。
  • @Amongalen 您的链接没有解释为什么它更好,只是它在 Java 中使用。 31 是根据经验选择的,以便在碰撞和有效计算之间进行权衡。 Knuth 研究了一般方法,使用多项式和素数幂。

标签: java hashcode


【解决方案1】:

如果你使用x+y那么如何区分点(3,4)和(4,3)?两者都将具有相同的哈希码...

虽然31 * x + y 并不完美,但在同样的情况下,它会好很多。

注意:根据散列的定义,没有完美的散列。唯一要做的就是分析给定哈希函数会发生什么样的冲突。在几何情况下,第一个为非常简单和常见的对称属性引入了碰撞。因此,在非常常见的情况下,可能会发生太多冲突。

【讨论】:

  • 在第一种情况下,(3,4) 和 (4,3) 具有相同的哈希码。在第二种情况下,(0,31) 和 (1,0) 具有相同的哈希码。为什么第二个比第一个好?
  • @Amongalen 因为等价函数诱导的几何特性在第二种情况下更为不寻常。
  • @Amongalen,这也可以通过乘以第二项来解决,例如:31 * x + 37 * y
  • @Titulum 然后其他一些点将相互碰撞,例如。 (0,31) 和 (37, 0) :)
  • @Titulum 没办法。将两个整数编码为一个 ===> 碰撞,无论您做什么。
【解决方案2】:

假设您有两个字符串属性prop1prop2,以及两个对象:

A: {prop1="foo", prop2="bar"}
B: {prop1="bar", prop2="foo"}

这些显然是不同的值,设置哈希码来区分它们很有用。如果您只是简单地将属性的哈希码相加,您将得到AB 的相同值。相反,通过相乘和相加,哈希码会根据属性顺序而有所不同。

您似乎可能对建议稍有误解:乘加的目的是创建对对象内属性的语义顺序的依赖,而不是执行计算顺序

【讨论】:

  • >>> "foo" * 37 + "bar" 'foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoobar' >>>
【解决方案3】:

Jean-Baptiste Yunès 的答案是正确的,但我将添加以下示例来说明(请记住,它是在 javascript 中,只是因为我为示例快速实现了这一点):

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
}

function getHashCollisions(collection, hashFunction) {
    const collisionMap = new Map();
    let count = 1;
    let total = collection.length;
    for (const point of collection) {
        console.log(`calculating ${count++}/${total}`);
        const currentHash = hashFunction(point);
        const hashCount = collisionMap.has(currentHash) ? collisionMap.get(currentHash) +1 : 1;
        collisionMap.set(currentHash, hashCount);
    }
    return collisionMap;
}

function generateDataset(rangeX, rangeY) {
    const points = [];
    let count = 1;
    for (let x = 0; x < rangeX; x++) {
        for (let y = 0; y < rangeY; y++) {
            console.log(`generating ${count++} Point(${x};${y})`);
            points.push(new Point(x, y));
        }
    }
    return points;
}

function calculateAndGenerateReport(dataset, hashFunction, hashFunctionName) {
    const hashes = getHashCollisions(dataset, hashFunction);
    const totalCollisions = Array.from(hashes.values()).filter(currentCollisionCount => currentCollisionCount > 1).length;
    const highestCollisionCount = Array.from(hashes.values()).reduce((currentHighest, current) => current > currentHighest ? current : currentHighest) - 1;
    return `${hashFunctionName}: ${totalCollisions} collisions, highest collision count: ${highestCollisionCount}`;
}

const dataset = generateDataset(100, 100);

const literalHashesReport = calculateAndGenerateReport(dataset, point => point.x + point.y, "literal hash function:");
const onePrimeHashesReport = calculateAndGenerateReport(dataset, point => 31 * point.x + point.y, "one prime multiplication hash function:");
const twoPrimesHashesReport = calculateAndGenerateReport(dataset, point => 31 * point.x + 37 * point.y, "two primes multiplication hash function:");
const twoLargePrimesHashesReport = calculateAndGenerateReport(dataset, point => 8191 * point.x + 131071 * point.y, "two large primes multiplication hash function:");

console.log(literalHashesReport);
console.log(onePrimeHashesReport);
console.log(twoPrimesHashesReport);
console.log(twoLargePrimesHashesReport)

结果:

literal hash function: 197 collisions, highest collision count: 99
one prime multiplication hash function: 3107 collisions, highest collision count: 3
two primes multiplication hash function: 3359 collisions, highest collision count: 2
two large primes multiplication hash function: 0 collisions, highest collision count: 0

这表明我们选择“计算”散列的(素数)数字大大降低了冲突的概率。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-06-26
    • 1970-01-01
    • 2019-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多