Java中用于文本字符串的64位哈希函数是什么?

我正在寻找一个散列函数:

  1. 很好地哈希文本字符串(例如,很少冲突)
  2. 用Java编写,并被广泛使用
  3. 奖金:适用于多个字段(而不是我将它们串联并在连接的字符串上应用哈希)
  4. 奖励:具有128位变量。
  5. 奖励:不占用CPU。
ripper234 asked 2019-11-18T15:53:18Z
9个解决方案
64 votes

为什么不使用默认的2619841162052051585025的BigInteger变体(有些真正聪明的人肯定会努力使其变得高效-更不用说已经看过此代码的数千名开发人员的眼睛了)?

// adapted from String.hashCode()
public static long hash(String string) {
  long h = 1125899906842597L; // prime
  int len = string.length();

  for (int i = 0; i < len; i++) {
    h = 31*h + string.charAt(i);
  }
  return h;
}

如果您正在寻找更多位,则可以使用BigInteger编辑:

正如我在对@brianegge的回答的评论中提到的那样,对于32位以上的哈希没有太多用例,对于64位以上的哈希,很可能没有一个用例:

我可以想象一个分布在数十台服务器上的巨大哈希表,也许存储了数百亿个映射。 对于这种情况,@ brianegge在这里仍然有一个有效点:32位允许2 ^ 32(约43亿)个不同的哈希键。 假设算法很强大,您仍然应该有很少的冲突。 使用64位(184,744,470,073亿个不同的密钥),无论您需要哪种疯狂的方案,都可以节省。 但是,对于128位密钥(340,282,366,920,938,463,463,374,607,607,431,430亿个可能的密钥)的用例的思考几乎是不可能的。

要合并多个字段的哈希,只需将X与一个素数相乘,然后将它们相加即可:

long hash = MyHash.hash(string1) * 31 + MyHash.hash(string2);

此处使用小质数,以避免交换值具有相等的哈希码,即{'foo','bar'}和{'bar','foo'}不相等,应具有不同的哈希码。 XOR不好,因为如果两个值相等,它将返回0。 因此,{'foo','foo'}和{'bar','bar'}将具有相同的哈希码。

sfussenegger answered 2019-11-18T15:54:14Z
4 votes

创建一个SHA-1哈希,然后屏蔽掉最低的64位。

Aaron Digulla answered 2019-11-18T15:54:39Z
3 votes
long hash = string.hashCode();

是的,高32位将为0,但是在遇到散列冲突问题之前,您可能会耗尽硬件资源。 String中的hashCode非常有效且经过了良好的测试。

更新资料我认为以上内容满足了可能会起作用的最简单的方法,但是,我同意@sfussenegger扩展现有String hashCode的想法。

除了为您的String提供良好的hashCode外,您可能还需要考虑在实现中重新哈希该哈希码。 如果您的存储被其他开发人员使用或与其他类型一起使用,则可以帮助分发密钥。 例如,Java的HashMap基于2的幂的哈希表,因此它添加了此功能以确保低位充分分布。

    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
brianegge answered 2019-11-18T15:55:20Z
2 votes

为什么不使用CRC64多项式。 这些都是相当有效和经过优化的,以确保对所有位进行计数并分布在结果空间中。

如果您在Google中搜索“ CRC64 Java”,则网络上有很多可用的实现。

Peter Tillemans answered 2019-11-18T15:55:52Z
2 votes

今天(2018)的答案。 SipHash。

它比这里的大多数答案要快得多,并且质量要比所有答案都要高得多。

番石榴库有一个:[https://google.github.io/guava/releases/23.0/api/docs/com/google/common/hash/Hashing.html#sipHash24--]

Scott Carey answered 2019-11-18T15:56:32Z
1 votes

做这样的事情:

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class Test {

    public static void main(String[] args) throws NoSuchAlgorithmException,
            IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(baos);

        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            SomeObject testObject = new SomeObject();

            dos.writeInt(testObject.count);
            dos.writeLong(testObject.product);
            dos.writeDouble(testObject.stdDev);
            dos.writeUTF(testObject.name);
            dos.writeChar(testObject.delimiter);
            dos.flush();

            byte[] hashBytes = md.digest(baos.toByteArray());
            BigInteger testObjectHash = new BigInteger(hashBytes);

            System.out.println("Hash " + testObjectHash);
        } finally {
            dos.close();
        }
    }

    private static class SomeObject {
        private int count = 200;
        private long product = 1235134123l;
        private double stdDev = 12343521.456d;
        private String name = "Test Name";
        private char delimiter = '\n';
    }
}

DataOutputStream使您可以编写基元和字符串,并将它们输出为字节。 在其中包装ByteArrayOutputStream可以使您写入一个字节数组,该数组与MessageDigest很好地集成在一起。 您可以从此处列出的任何算法中进行选择。

最终,BigInteger使您可以将输出字节转换为易于使用的数字。 MD5和SHA1算法都产生128位哈希,因此,如果需要64位哈希,则可以截断。

SHA1应该可以很好地对几乎所有内容进行哈希处理,并且不经常发生冲突(128位)。 这可以从Java运行,但是我不确定如何实现。 它实际上可能相当快。 它在我的实现中的多个领域都起作用:只需将它们全部推入MessageDigest.getInstance(),您就可以开始了。 您甚至可以使用反射和注释(也许是clone()来显示哪些字段以散列的顺序排列)。 它有一个128位的变体,我想您会发现它使用的CPU不如您想象的那样多。

我使用这样的代码来获取大型数据集(目前可能有数十亿个对象)的哈希值,以便能够在许多后端存储中将它们分片。 它应该可以满足您的任何需求。 请注意,我认为您可能只想打一次MessageDigest.getInstance(),然后再打clone():IIRC克隆速度要快得多。

jasonmp85 answered 2019-11-18T15:57:28Z
1 votes

反转字符串以获取另一个32位哈希码,然后将两者结合起来:

String s = "astring";
long upper = ( (long) s.hashCode() ) << 32;
long lower = ( (long) s.reverse().hashCode() ) - ( (long) Integer.MIN_VALUE );
long hash64 = upper + lower;

这是伪代码; String.reverse()方法不存在,需要以其他方式实现。

user2020240 answered 2019-11-18T15:58:03Z
0 votes

您看Apache Commons lang吗?

但是对于64位(和128位),您需要一些技巧:Joshua Bloch在《有效Java》一书中列出的规则可帮助您轻松创建64位哈希(只需使用long而不是int)。 对于128位,您需要其他技巧...

St.Shadow answered 2019-11-18T15:58:37Z
-2 votes

免责声明:如果您希望有效地散列单个自然语言单词,则可以使用此解决方案。 散列较长的文本或包含非字母字符的文本时效率很低。

我不知道函数,但是下面的想法可能会有所帮助:

  • 用64位中的52位表示字符串中存在哪些字母。 例如,如果存在“ a”,则将位[0]设置为“ b”,将位1设置为“ A”,将位[26]设置为“ 1”。 这样,只有包含完全相同的字母集的文本才具有相同的“签名”。

然后,您可以使用其余的12位来编码字符串长度(或字符串的模值)以进一步减少冲突,或者使用传统的哈希函数生成12位的hashCode。

假设您的输入为纯文本,我可以想象这将导致很少的冲突,并且计算成本不高(O(n))。 到目前为止,与其他解决方案不同,此方法考虑了问题域以减少冲突-它基于在Pearls编程中介绍的Anagram Detector(请参阅此处)。

Adamski answered 2019-11-18T15:59:40Z
translate from https://stackoverflow.com:/questions/1660501/what-is-a-good-64bit-hash-function-in-java-for-textual-strings