如何散列一个字符串,以便如果一个字符被修改,它几乎不会改变?

时间:2014-01-29 20:46:58

标签: java hash

拿这个代码

public static void main(String[] args) {
    System.out.println("1000000".hashCode());
    System.out.println("1000001".hashCode());
    System.out.println("2000000".hashCode());
}

这会产生输出

1958013297
1958013298
-1449450318

第一个是'控制'。在第二个我改变其中一个字符,它产生一个非常相似的哈希,但是如果我在开头修改一个字符,它给我一个完全不同的哈希。是否有一种方法可以使它不会影响散列的范围,无论你是否更改字符串的开头或结尾处的字符?

5 个答案:

答案 0 :(得分:1)

您所描述的内容可能不是一个好主意,因为它会导致无关联字符串的哈希值集群。这将导致散列表中的性能不佳,并且会在其他上下文中产生大量冲突。

也就是说,有很多简单的解决方案。您可以将字符的所有数值加在一起,这会使较小的更改仅影响哈希(但会给出可怕的分散)。作为极端情况,您可以将所有哈希值设置为0,这样可以在任何更改时使哈希不会更改。

希望这有帮助!

答案 1 :(得分:1)

在这里使用 hash 并不是一个好主意,因为如果只有输入的一个位发生变化,则哈希通常被设计为尽可能地不同(Java字符串显然不是在这里做得很好 - 但它也是very simple)。这有时在散列实现中称为雪崩效应。

您可以使用的是一些距离指标而不是哈希值,例如Hamming distance。或者,您可以尝试对字符串中的字符进行排序并对其进行哈希处理(但即使对于完全不同的字符串,这也可能会产生类似的哈希值)。

答案 2 :(得分:0)

以各种方式计算哈希值;大多数方法涉及素数和结构/类/ whathaveyou的每个成员的循环公式。更改字符串开头的字符将显着改变您的哈希值(例如,您不会注意到两个字符串只是通过查看其哈希码而相差一个字符。)

答案 3 :(得分:0)

也许您正在寻找单向哈希编码?如果是这种情况,您应该尝试使用base 64编码的SHA256哈希:

MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest("This is string".getBytes("UTF-8"));
System.out.println(DatatypeConverter.printBase64Binary(hash));

返回:

  • 1rx2SBPZH729fp91Vkx/bC2DD1dNdwA0i1NJiYOO+WQ= “这是字符串”
  • ld5Z0dyXI2k3oxkUqw62lQwa2kmqtfH28/nGagnx7bM= “这很强”

答案 4 :(得分:0)

从您的评论中听起来好像您真正想要的是将小组整数转换为“摘要”的方法,这样检查两个这样的组的摘要将指示它们是否可能相似。如果您不限于使用32位摘要,我建议使用64位摘要可以降低误报率。

为了给出一个更具体的例子,假设有人希望从6个数字中产生64位摘要,并且能够 - 通过比较两个事物的摘要,能够拒绝除了那些之外的任何事物。匹配完美,匹配六个项目中的五个,或两个项目中的1个。定义12种将整数映射到0到63之间的数字的方法,这样映射就像实际一样被加密。

然后计算

Digest = (1L << Scramble(0  , (Value[0]  ) >> 1) |
         (1L << Scramble(0+6, (Value[0]+1) >> 1) |
         (1L << Scramble(1  , (Value[1]  ) >> 1) |
         (1L << Scramble(1+6, (Value[1]+1) >> 1) |
         (1L << Scramble(2  , (Value[2]  ) >> 1) |
         (1L << Scramble(2+6, (Value[2]+1) >> 1) |
         (1L << Scramble(3  , (Value[3]  ) >> 1) |
         (1L << Scramble(3+6, (Value[3]+1) >> 1) |
         (1L << Scramble(4  , (Value[4]  ) >> 1) |
         (1L << Scramble(4+6, (Value[4]+1) >> 1) |
         (1L << Scramble(5  , (Value[5]  ) >> 1) |
         (1L << Scramble(5+6, (Value[5]+1) >> 1);

这将产生一个最多有12个位的数字。如果两个摘要足够接近,则列表可能被认为是近似匹配,则计算

long diff1 = digest1 & ~digest2;
diff1 &= (diff1-1);
diff1 &= (diff1-1);

应将diff1设置为零,与digest2 & ~digest1的类似计算也应该为零。请注意,将有许多完全不同的数字组合,其摘要将显示为“可能接近匹配”,但这种方法可以快速拒绝其中的绝大多数。如果再次重复diff1 &=...行,可以进一步增加容差。进一步注意,如果Scramble返回的值为0到31而不是0到63,那么算法在某种程度上可以使用int摘要,但“假命中”的数量会大大增加,除非结合到摘要中的值的数量减少了。

如果六个值不够,可以添加更多位(尽管这会增加错误命中的数量),或者可以通过将它们相加并将它们缩小来将多个值组合到每个值中。例如,如果将三个数字相加在一起并且一个想要匹配的东西除非它们被三个关闭,那么可以将总和减少八倍[如果所有匹配的除了两个值之外每个都关闭三个,那么sum将在6之内,这意味着将总和除以8将产生在一个内的数字。