将哈希值映射到范围,最小化冲突

时间:2015-11-22 23:43:48

标签: java hash hashtable

上下文

嗨,我正在为学校做作业,要求我们用Java实现哈希表。没有要求将碰撞保持在最低限度,但低碰撞率和速度似乎是我所做的所有reading (some more)中最受追捧的两种品质。< / p>

问题

我想了解如何将哈希函数的输出映射到较小范围的一些指导,而不会有> 20%的键碰撞(yikes)。

在我所研究的所有算法中,键被映射到无符号32位整数的整个范围(或者在很多情况下,64位,甚至128位)。我在这里,维基百科或我遇到的任何与哈希相关的文章/讨论中都没有找到太多关于此的内容。

就我的实施细节而言,我在Java(我的学校的任务)工作,这是有问题的,因为没有未签名的类型可以使用。为了解决这个问题,我一直在使用64位长整数类型,然后使用位掩码将其映射回32位。不是简单地截断,而是将前32位与底部32进行异或,然后执行按位AND以屏蔽掉任何可能导致负值的高位,当我将其转换为32位整数时。毕竟,一个单独的函数将得到的哈希值压缩到适合哈希表的内部数组的边界。

最终看起来像:

int hash( String key ) {

    long h;

    for( int i = 0; i < key.length(); i++ )
        //do some stuff with each character in the key

        h = h ^ ( h << 32 );

    return h & 2147483647;
}

内循环取决于散列函数(我实现了一些:多项式散列,FNV1,SuperFastHash和根据输入数据定制的自定义)。

他们基本上都表现得非常糟糕。我还没有看到&lt; 20%的键碰撞。甚至在我将哈希值压缩到数组索引之前,我的哈希函数都不会让我感谢10k冲突。我的输入是两个文本文件,每个〜220,000行。一个是英文单词,另一个是不同长度的随机字符串。

我的讲义推荐以下内容,用于压缩散列键:

(hashed key) % P

其中P是最大的素数&lt;内部数组的大小。

这是压缩哈希值的可接受方法吗?我觉得它不是,但由于即使在压缩之前表现也很差,我也觉得它不是主要的罪魁祸首。

1 个答案:

答案 0 :(得分:1)

我不知道我是否理解你的具体问题,但我会尽力帮助解决哈希表现和碰撞问题。

基于散列的对象将根据散列值确定它们将在哪个存储桶中存储键值对。在每个桶中,存在一对结构(在HashMap情况下为LinkedList)。

如果哈希值通常相同,那么存储桶通常会相同,因此性能会下降很多,让我们看一个例子:

考虑这个课程

package hashTest;

import java.util.Hashtable;

public class HashTest {

    public static void main (String[] args) {

        Hashtable<MyKey, String> hm = new Hashtable<>();

        long ini = System.currentTimeMillis();

        for (int i=0; i<100000; i++) {
            MyKey a = new HashTest().new MyKey(String.valueOf(i));

            hm.put(a, String.valueOf(i));
        }

        System.out.println(hm.size());

        long fin = System.currentTimeMillis();
        System.out.println("tiempo: " + (fin-ini) + " mls");
    }

    private class MyKey {

        private String str;

        public MyKey(String i) {
            str = i;
        }

        public String getStr() {
            return str;
        }

        @Override
        public int hashCode() {
            return 0;
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof MyKey) {
                MyKey aux = (MyKey) o;
                if (this.str.equals(aux.getStr())) {
                    return true;
                }
            }
            return false;
        }
    }
}

请注意,类MyKey中的hashCode总是返回'0'作为哈希。哈希码定义(http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#hashCode())没问题。如果我们运行该程序,这就是结果

100000 
tiempo: 62866 mls

性能非常差,现在我们要更改MyKey哈希码代码:

package hashTest;

import java.util.Hashtable;

public class HashTest {

    public static void main (String[] args) {

        Hashtable<MyKey, String> hm = new Hashtable<>();

        long ini = System.currentTimeMillis();

        for (int i=0; i<100000; i++) {
            MyKey a = new HashTest().new MyKey(String.valueOf(i));

            hm.put(a, String.valueOf(i));
        }

        System.out.println(hm.size());

        long fin = System.currentTimeMillis();
        System.out.println("tiempo: " + (fin-ini) + " mls");
    }

    private class MyKey {

        private String str;

        public MyKey(String i) {
            str = i;
        }

        public String getStr() {
            return str;
        }

        @Override
        public int hashCode() {
            return str.hashCode() * 31;
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof MyKey) {
                MyKey aux = (MyKey) o;
                if (this.str.equals(aux.getStr())) {
                    return true;
                }
            }
            return false;
        }
    }
}

请注意,只有MyKey中的哈希码发生了变化,现在当我们运行代码时,结果是

100000
tiempo: 47 mls

现在有一个令人难以置信的更好的表现,只需稍作改动。一种非常常见的做法是返回哈希码乘以素数(在本例中为31),使用在equals方法中使用的相同哈希码成员,以确定两个对象是否相同(在这种情况下只是str)。

我希望这个小例子可以为你的问题指出一个解决方案。