一个很好的哈希函数用于采访整数,字符串?

时间:2011-05-21 16:12:35

标签: java hash-function


我在采访中遇到过需要对整数或字符串使用哈希函数的情况。在这种情况下我们应该选择哪一种?我在这些情况下错了,因为我最终选择了产生大量碰撞的那些,但是哈希函数往往是数学的,你不能在面试中回忆起它们。是否有任何一般性建议,以至于面试官对整数或字符串输入的方法感到满意?在“面试情境”中,哪些功能对于两种输入都是​​足够的

3 个答案:

答案 0 :(得分:8)

以下是来自Effective java page 33的简单食谱:

  1. 在名为result的int变量中存储一些常量非零值,例如17。
  2. 对于您对象中的每个重要字段f(每个字段都考虑在内 等于方法,即),执行以下操作:
    1. 计算字段的int哈希码c:
      • 如果字段是布尔值,则计算(f?1:0)。
      • 如果字段是byte,char,short或int,则计算(int)f。
      • 如果字段是long,则计算(int)(f ^(f>>>> 32))。
      • 如果该字段是浮点数,请计算Float.floatToIntBits(f)。
      • 如果该字段是double,则计算Double.doubleToLongBits(f)和 然后在步骤2.1.iii。
      • 中散列生成的long
      • 如果该字段是对象引用,则此类的等于方法 通过递归地递归调用equals来比较该字段 在字段上调用hashCode。如果进行更复杂的比较 必需,计算此字段的“规范表示” 在规范表示上调用hashCode。如果是的价值 field为null,返回0(或其他一些常量,但0是传统的)。 48第3章所有对象共有的方法
      • 如果字段是数组,则将其视为每个元素都是单独的字段。 也就是说,通过应用计算每个重要元素的哈希码 这些规则是递归的,并按步骤2.b组合这些值。如果每一个 数组字段中的元素很重要,可以使用其中之一 版本1.5中添加了Arrays.hashCode方法。
    2. 将步骤2.1中计算的哈希码c合并到结果中,如下所示: 结果= 31 *结果+ c;
    3. 返回结果。
    4. 完成hashCode方法的编写后,问问自己是否 相等的实例具有相同的哈希码。编写单元测试以验证您的直觉! 如果相等的实例具有不相等的哈希码,请弄清楚原因并解决问题。

答案 1 :(得分:5)

你应该问面试官哈希函数的用途 - 这个问题的答案将决定哪种哈希函数是合适的。

  • 如果它用于散列数据结构之类的哈希映射,您希望它尽可能简单(快速执行)并避免冲突(最常见的值映射到不同的哈希值) )。一个很好的例子是对同一个整数进行整数散列 - 这是java.lang.Integer中的标准hashCode()实现

  • 如果是出于安全目的,您需要使用cryptographic hash function。这些主要是为了很难反转哈希函数或发现冲突。

  • 如果您想要快速伪随机哈希值(例如,用于模拟),那么您通常可以修改伪随机数生成器来创建这些值。我个人最喜欢的是:

public static final int hash(int a) {         
      a ^= (a << 13);
      a ^= (a >>> 17);        
      a ^= (a << 5);
      return a;   
}

如果要为某种形式的复合结构计算哈希值(例如,包含多个字符的字符串,或数组,或具有多个字段的对象),则可以使用各种技术来创建组合哈希函数。我建议对组成部分的旋转散列值进行异或,例如:

public static <T> int hashCode(T[] data) {
    int result=0;
    for(int i=0; i<data.length; i++) {
        result^=data[i].hashCode();
        result=Integer.rotateRight(result, 1);
    }
    return result;
}

请注意,上述内容不具有加密安全性,但可用于大多数其他目的。您显然会遇到冲突,但是当将大型结构散列为整数时,这是不可避免的: - )

答案 2 :(得分:2)

  

对于整数,我通常使用k%p,其中p =哈希表的大小并且是素数,对于字符串,我从String类中选择哈希码。这足以接受一家大型科技公司的采访吗? - 凤凰2天前

也许不是。需要向哈希表提供哈希函数并不罕见,哈希表的实现对您来说是未知的。此外,如果您以一种依赖于使用素数桶的实现的方式进行散列,那么如果实现由于新的库,编译器,OS端口等而发生更改,则性能可能会降低。

就我个人而言,我认为在访谈中重要的是清楚地理解通用哈希算法的理想特征,这基本上是对于任何两个输入键,其值的变化小到一位,每一个输出中的位有大约50/50的翻转几率。我发现这很反直觉,因为我第一次看到的很多散列函数都使用了位移和XOR,而翻转的输入位通常会翻转一个输出位(通常在另一个位位置,因此1输入位影响很多) -output-bits是一个小小的启示时刻,当我在Knuth的一本书中读到它时。凭借这些知识,你至少能够测试和评估具体的实现,而不管它们是如何实现的。

我会提到一种方法,因为它实现了这种理想并且易于记忆,尽管内存使用可能比数学方法慢(也可能更快,取决于硬件),只需使用输入中的每个字节查找随机整数表。例如,给定24位RGB值和int table[3][256]table[0][r] ^ table[1][g] ^ table[2][b]是一个很好的sizeof int哈希值 - 如果输入通过int值随机分散,则确实“完美” (而不是说递增 - 见下文)。这种方法对于长或任意长度的密钥并不理想,但您可以开始重新访问表并对值进行位移等。

所有这些说明,对于您了解输入键中的模式和/或所涉及的桶数(例如,您)的特定情况,您可以有时比这种随机方法做得更好可能知道输入键从1到100是连续的,并且有128个桶,因此您可以在没有任何冲突的情况下传递密钥。但是,如果输入不再符合您的期望,那么您可能会遇到可怕的碰撞问题,而“随机化”方法永远不会比负载(size()/ buckets)暗示的更糟糕。另一个有趣的见解是,当你想要一个快速和平庸的哈希时,你不必在生成哈希时包含所有输入数据:例如上次我看了Visual C ++的字符串哈希代码时,它选择了沿文本均匀分布的十个字母作为输入....