如何实现散列函数`h(k)=(A·k mod 2 ^ w)>> (w - r)`用Java

时间:2012-05-02 15:11:35

标签: java hash-function

重要通知:

对于人们来说,这不是一个讨论线程,可以让我对哈希有所了解。我只需要知道如何使给定的函数在java中工作 - 一个例子就是最好的。

问题:

我试图通过麻省理工学院计算机科学教授(http://videolectures.net/mit6046jf05_leiserson_lec08/)观看两个免费讲座,以便磨练我对哈希函数的理解。所以在讲座之后,我试图在java中实现以下哈希函数。

h(k) = (A·k mod 2^w) >> (w – r)
WHERE
r: m, the size of the array, is a power of 2 such that m=2^r
w: the computer has w-bit words, such as 32-bit or 64-bit computer
k: the value I am to find a key for
A: a random odd number (prime would be great) between 2^(w-1) and 2^w    

我认为这很容易在java中实现。但是当我做2 ^ w,其中w = 32时,我在Java中得到的结果不准确。在现实生活中2^32 = 4294967296但不在java中,将结果截断为2^31 - 12147483647

有谁知道如何解决这个问题,以便在Java中实现该功能?

编辑:

我看到很多回复集中在32.如果我的电脑是64位怎么办?我很难设置w = 32,因为我使用的是Java?

4 个答案:

答案 0 :(得分:4)

有些术语是多余的,因为Java无论如何都会采用这种行为。

A·k mod 2^w

在Java中,整数乘法溢出,因此执行mod 2^w(带符号)。如果你移动了至少一位,它有一个符号的事实并不重要。

(w - r)的移位与Java中-r的移位相同(类型隐含w)

private static final int K_PRIME = (int) 2999999929L;

public static int hash(int a, int r) {
   // return (a * K_PRIME % (2^32)) >>> (32 - r);
   return (a * K_PRIME) >>> -r;
}

表示64位

private static final long K_PRIME = new BigInteger("9876534021204356789").longValue();

public static long hash(long a, int r) {
    // return (a * K_PRIME % (2^64)) >>> (64 - r);
    return (a * K_PRIME) >>> -r;
}

我写了这个例子,表明你可以在BigInteger中做同样的事情,为什么你不这样做。 ;)

public static final BigInteger BI_K_PRIME = new BigInteger("9876534021204356789");
private static long K_PRIME = BI_K_PRIME.longValue();

public static long hash(long a, int r) {
    // return (a * K_PRIME % (2^64)) >>> (64 - r);
    return (a * K_PRIME) >>> -r;
}

public static long biHash(long a, int r) {
    return BigInteger.valueOf(a).multiply(BI_K_PRIME).mod(BigInteger.valueOf(2).pow(64)).shiftRight(64 - r).longValue();
}

public static void main(String... args) {
    Random rand = new Random();
    for (int i = 0; i < 10000; i++) {
        long a = rand.nextLong();
        for (int r = 1; r < 64; r++) {
            long h1 = hash(a, r);
            long h2 = biHash(a, r);
            if (h1 != h2)
                throw new AssertionError("Expected " + h2 + " but got " + h1);
        }
    }

    int runs = 1000000;
    long start1 = System.nanoTime();
    for (int i = 0; i < runs; i++)
        hash(i, i & 63);
    long time1 = System.nanoTime() - start1;

    long start2 = System.nanoTime();
    for (int i = 0; i < runs; i++)
        biHash(i, i & 63);
    long time2 = System.nanoTime() - start2;
    System.out.printf("hash with long took an average of %,d ns, " +
            "hash with BigInteger took an average of %,d ns%n",
            time1 / runs, time2 / runs);
}

打印

hash with long took an average of 3 ns, \
    hash with BigInteger took an average of 905 ns

答案 1 :(得分:2)

intlong都不足以容纳2 ^(w-1)中所需的所有值。您最好使用BigInteger

答案 2 :(得分:1)

让我们看看number % 2^32实际上做了什么:它得到除法的余数2 ^ 32。如果您的范围是0到2 ^ 32,计算机将自动为您执行模数,因为它会丢弃2 ^ 32以上的所有内容。

让我们取8而不是32,并切换到二进制数系统:

  1000 1000 % 1 0000 0000 = 1000 1000
1 1000 1000 % 1 0000 0000 = 1000 1000

所以你应该做的是将数量限制在计算机的范围内。如果您愿意使用,例如c ++,就像将值声明为unsigned int一样简单。上面第二个示例的第一个1将被截断,因为它不适合变量。

在java中,您没有无符号整数。如果您计算A * k,并且导致溢出,则可能会获得签名值。但是,接下来你唯一需要做的就是做一个正确的转变,这应该不重要。

所以我的建议是简单地放弃模数计算。尝试一下,我不太确定它是否有效。

答案 3 :(得分:0)

Java主要int的最小值范围为-2,147,483,648,最大值为2,147,483,647

查看此link以了解有关原始信息的详细信息。

我建议使用long代替int