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

时间:2009-11-02 10:35:33

标签: java string hash 64-bit collision

我正在寻找一个哈希函数:

  1. 哈希文字字符串好(例如几次碰撞)
  2. 是用Java编写的,并且被广泛使用
  3. 奖励:适用于多个字段(而不是我将它们连接起来并在连接字符串上应用哈希值)
  4. Bonus:有128位变体。
  5. 奖励:不是CPU密集型。

9 个答案:

答案 0 :(得分:63)

为什么不使用默认long的{​​{1}}变体(其中一些非常聪明的人肯定会努力提高效率 - 不要提到成千上万的开发者眼睛已经看过这个代码)?

String.hashCode()

如果您正在寻找更多位,您可以使用// 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; } 编辑:

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

  

我可以想象一个巨大的哈希表分布在几十台服务器上,可能存储了数百亿的映射。对于这种情况,@ brianegge在这里仍然有一个有效点:32位允许2 ^ 32(约43亿)个不同的散列键。假设一个强大的算法,你应该仍然有很少的碰撞。使用64位(18,446,744,073亿个不同的键),无论您需要什么样的疯狂场景,都可以保存。考虑使用128位密钥的用例(340,282,366,920,938,463,463,374,607,431亿个可能的密钥)几乎是不可能的。

要组合多个字段的哈希值,只需执行XOR 与一个素数相乘并添加它们:

BigInteger

小素数在那里以避免切换值的相等哈希码,即{'foo','bar'}和{'bar','foo'}不相等并且应该具有不同的哈希码。如果两个值相等,则XOR为坏,因为它返回0。因此,{'foo','foo'}和{'bar','bar'}将具有相同的哈希码。

答案 1 :(得分:4)

Create an SHA-1 hash然后屏蔽最低的64位。

答案 2 :(得分:3)

long hash = string.hashCode();

是的,前32位将为0,但在遇到哈希冲突问题之前,您可能会耗尽硬件资源。 String中的hashCode效率很高,经过了充分测试。

<强>更新 我认为上面的内容满足了最简单的可能有用的东西,但是,我同意@sfussenegger关于扩展现有String hashCode的想法。

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

    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);

答案 3 :(得分:2)

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

如果你谷歌“CRC64 Java”

,网上有很多可用的实现

答案 4 :(得分:1)

做这样的事情:

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很好地集成。您可以从列出here列出的任何算法中选择。

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

SHA1应该散列几乎所有东西,并且偶尔会发生冲突(它是128位)。这适用于Java,但我不确定它是如何实现的。它实际上可能相当快。它适用于我的实现中的几个领域:只需将它们全部推送到DataOutputStream,你就可以了。您甚至可以使用反射和注释(可能@HashComponent(order=1)来显示哪些字段进入哈希以及按什么顺序)。它有一个128位的变体,我想你会发现它不会像你想象的那样使用那么多的CPU。

我已经使用这样的代码来获取大数据集的哈希值(现在可能是数十亿个对象),以便能够在许多后端存储中对它们进行分片。它应该适用于你需要的任何东西。请注意,我认为您可能只需要调用MessageDigest.getInstance()一次,然后再调用clone():IIRC克隆速度要快得多。

答案 5 :(得分:1)

将字符串反转以获取另一个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()方法不存在,需要以其他方式实现。

答案 6 :(得分:1)

今天(2018年)的答案。 SipHash。

这将比这里的大多数答案快得多,并且质量明显高于所有答案。

Guava图书馆有一个:https://google.github.io/guava/releases/23.0/api/docs/com/google/common/hash/Hashing.html#sipHash24--

答案 7 :(得分:0)

你看Apache commons lang吗?

但是对于64位(和128位),你需要一些技巧:Joshua Bloch的书“Effective Java”中规定的规则可以帮助你轻松创建64位散列(只需使用long而不是int)。对于128位,你需要额外的黑客......

答案 8 :(得分:-2)

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

我不知道某个功能,但这里有一个可能会有所帮助的想法:

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

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

假设您的输入是纯文本的,我可以想象这会导致很少的冲突并且计算成本很低(O(n))。 与其他解决方案不同,到目前为止,这种方法将问题域考虑在内以减少碰撞 - 它基于编程珍珠中描述的Anagram Detector(参见here)。