【问题标题】:Good Hash Function for Strings良好的字符串散列函数
【发布时间】:2011-02-07 03:03:07
【问题描述】:

我正在尝试为字符串想出一个好的散列函数。而且我认为总结字符串中前五个字符的 unicode 值可能是一个好主意(假设它有五个,否则在它结束的地方停止)。这是个好主意,还是坏主意?

我在 Java 中执行此操作,但我不认为这会产生很大的不同。

【问题讨论】:

  • 好的散列函数在很大程度上取决于散列的输入和算法的要求。例如,如果所有字符串都以相同的五个字符开头,那么这样的哈希就不是很好。它也往往会导致正态分布。
  • 98153的可能重复
  • 为什么不能使用String自己的hashCode()
  • @WhirlWind,真的,我不确定字符串会有什么,除了它可能是英文文本。
  • @Barl,主要是因为我的教授告诉我们实现我们自己的哈希函子......而我不想使用 Java 的原因是因为它是通用的,我想更多具体的哈希函子会更好。

标签: java hash hashtable hashcode


【解决方案1】:

通常哈希不会求和,否则 stoppots 将具有相同的哈希。

并且您不会将其限制为前 n 个字符,因为否则 house 和 house 将具有相同的哈希值。

通常散列取值并将其乘以素数(使其更有可能生成唯一散列)因此您可以执行以下操作:

int hash = 7;
for (int i = 0; i < strlen; i++) {
    hash = hash*31 + charAt(i);
}

【讨论】:

  • @jonathanasdf 你怎么能说它总是给你一个唯一的哈希键。有数学证明吗?我认为我们必须用另一个更大的素数来取散列的 mod,否则会出现溢出问题。
  • @devsda 他并没有说总是独一无二的,他说更有可能是独一无二的。至于为什么,在 google 上快速搜索会发现这篇文章:computinglife.wordpress.com/2008/11/20/…解释了为什么 31 用于 Java 字符串散列。没有给出数学证明,但它确实解释了为什么素数效果更好的一般概念。
  • 非常感谢您阐明了更好地散列的想法。只是要仔细检查 - Java 将使用 hashCode() 返回值在存储对象之前映射到某个表索引。因此,如果 hashCode() 返回 m,它会执行类似 (m mod k) 的操作来获取大小为 k 的表的索引。对吗?
  • 这太棒了,你不知道它有多大帮助。我知道使用 31 可以获得最好的独特结果,但是 7 也是最好的吗?还是你只是选择了一个随机素数?
  • 我通过将最终结果 mod 字符串的长度来减少我的碰撞。 (我在 python 中工作,所以我不得不稍微改变一下)
【解决方案2】:

如果您在 Java 中执行此操作,那么您为什么要这样做?只需在字符串上调用.hashCode()

【讨论】:

  • 我在课堂上做这件事,部分作业是编写几个不同的哈希函数。教授告诉我们要为“更好”的人寻求外部帮助。
  • 如果您需要在 JVM 版本和实现之间保持一致,则不应依赖 .hashCode()。而是使用一些已知的算法。
  • String::hashCode 的算法是在 JDK 中指定的,因此它与 java.lang.String 类的存在一样具有可移植性。
【解决方案3】:
// djb2 hash function
unsigned long hash(unsigned char *str)
{
    unsigned long hash = 5381;
    int c;

    while (c = *str++)
        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */

    return hash;
}

source Logic behind djb2 hash function - SO

【讨论】:

  • 我认为它只是一个质数开始,所以我们有更少的碰撞。
【解决方案4】:

您可能应该使用String.hashCode()

如果你真的想自己实现hashCode:

不要试图排除 对象的重要部分来自 哈希码计算以改进 性能——Joshua Bloch,Effective Java

只使用前五个字符是一个坏主意。考虑分层名称,例如 URL:它们都将具有相同的哈希码(因为它们都以“http://”开头,这意味着它们存储在哈希映射中的同一个桶下,表现出糟糕的性能。

这是一个用来自“Effective Java”的字符串 hashCode 解释的战争故事:

实现的字符串哈希函数 在 1.2 之前的所有版本中检查 最多十六个字符,均匀 在整个字符串中间隔,开始 与第一个字符。对于大 分层名称的集合, 例如 URL,这个哈希函数 表现出可怕的行为。

【讨论】:

  • 如果使用双散列集合,让第一个散列非常快速和肮脏可能是值得的。如果一个人有一千个长字符串,其中一半是由一个糟糕的函数映射到一个特定的值,而其中的一半被映射到不同的值,那么单哈希表的性能会很差,但是双哈希表的性能会很差。散列表,其中第二个散列检查整个字符串,可能几乎是单个散列表的两倍(因为一半的字符串不必完全散列)。但是,没有一个标准的 Java 集合做双重散列。
【解决方案5】:

如果是安全问题,您可以使用 Java 加密:

import java.security.MessageDigest;

MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(stringToHash.getBytes());
String stringHash = new String(messageDigest.digest());

【讨论】:

  • 不错。我有一个机器学习应用程序,在大型语料库上进行统计 NLP。在对文本中的原始单词进行几次形态规范化的初始传递之后,我丢弃了字符串值并改用哈希码。在我的整个语料库中,大约有 600,000 个唯一词,并且使用默认的 java hashcode 函数,我得到了大约 3.5% 的冲突。但如果我对字符串值进行 SHA-256 处理,然后从消化后的字符串生成哈希码,则冲突率小于 0.0001%。谢谢!
  • @benjismith 百万分之一太大了...“小于 0.0001%”是“正好 0”的一种间接表达方式吗?我真的怀疑您是否看到了 SHA-256 碰撞,因为从未在任何地方、任何地方观察到过这种情况;甚至对于 160 位 SHA-1 也不行。如果您有两个产生相同 SHA-256 的字符串,安全社区会很乐意看到它们;你会以一种非常不起眼的方式闻名于世。见Comparison of SHA Functions
  • @TimSylvester,你误会了。我没有发现 SHA-256 冲突。我计算了 SHA-256,然后将得到的字节序列输入到一个典型的 Java“hashCode”函数中,因为我需要一个 32 位的散列。那就是我发现碰撞的地方。没什么了不起的:)
  • “散列”和“加密”之间没有区别吗?我了解 MessageDigest 是一种单向散列函数,对吗?此外,当我使用该函数时,当我在 LibreOffice 中打开文件时,我得到了很多垃圾 UTF 字符的散列字符串。是否可以将散列字符串作为随机的字母数字字符而不是垃圾 UTF 字符来获取?
  • String encryptedStringstringToEncrypt.getBytes() 指的是加密,而这确实是一种哈希算法。
【解决方案6】:

如果您想查看行业标准实施,我会查看java.security.MessageDigest

“消息摘要是一种安全的单向散列函数,它采用任意大小的数据并输出固定长度的散列值。”

【讨论】:

    【解决方案7】:

    据传FNV-1 是一个很好的字符串散列函数。

    对于长字符串(例如,超过 200 个字符),您可以从 MD4 哈希函数中获得良好的性能。作为一个加密功能,它在大约 15 年前就被破解了,但对于非加密用途,它仍然非常好,而且速度惊人。在 Java 的上下文中,您必须将 16 位 char 值转换为 32 位字,例如通过将这些值组合成对。可以在sphlib 中找到 Java 中 MD4 的快速实现。在课堂作业的情况下可能有点矫枉过正,但在其他方面值得一试。

    【讨论】:

    • 这个散列函数比java自带的要好很多。
    【解决方案8】:

    Nick 提供的这个函数很好,但是如果你使用 new String(byte[] bytes) 转换为 String 就失败了。你可以使用这个函数来做到这一点。

    private static final char[] hex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
    
    public static String byteArray2Hex(byte[] bytes) {
        StringBuffer sb = new StringBuffer(bytes.length * 2);
        for(final byte b : bytes) {
            sb.append(hex[(b & 0xF0) >> 4]);
            sb.append(hex[b & 0x0F]);
        }
        return sb.toString();
    }
    
    public static String getStringFromSHA256(String stringToEncrypt) throws NoSuchAlgorithmException {
        MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
        messageDigest.update(stringToEncrypt.getBytes());
        return byteArray2Hex(messageDigest.digest());
    }
    

    这可能对某人有帮助

    【讨论】:

    • 您可以将字节数组传递给 messageDigest.update()。
    【解决方案9】:

    Guava's HashFunction (javadoc) 提供了不错的非加密强度哈希。

    【讨论】:

      【解决方案10】:
               public String hashString(String s) throws NoSuchAlgorithmException {
          byte[] hash = null;
          try {
              MessageDigest md = MessageDigest.getInstance("SHA-256");
              hash = md.digest(s.getBytes());
      
          } catch (NoSuchAlgorithmException e) { e.printStackTrace(); }
          StringBuilder sb = new StringBuilder();
          for (int i = 0; i < hash.length; ++i) {
              String hex = Integer.toHexString(hash[i]);
              if (hex.length() == 1) {
                  sb.append(0);
                  sb.append(hex.charAt(hex.length() - 1));
              } else {
                  sb.append(hex.substring(hex.length() - 2));
              }
          }
          return sb.toString();
      }
      

      【讨论】:

        【解决方案11】:

        在尝试为字符串开发良好的 hast 函数时,使用奇数是一个好主意。这个函数接受一个字符串并返回一个索引值,到目前为止它的工作还不错。并且碰撞较少。指数范围从 0 到 300 甚至可能更多,但到目前为止,即使使用像“机电工程”这样的长词,我也没有得到任何更高的值

        int keyHash(string key)
        {
            unsigned int k = (int)key.length();
            unsigned int u = 0,n = 0;
        
            for (Uint i=0; i<k; i++)
            {
                n = (int)key[i];
                u += 7*n%31;
            }
            return u%139;
        }
        

        您可以做的另一件事是将每个字符 int parse 乘以索引,因为它像单词“bear”一样增加 (0*b) + (1*e) + (2*a) + (3*r) 这会给你一个 int 值来玩。上面的第一个哈希函数在“这里”和“听到”处发生碰撞,但仍然很擅长提供一些好的独特值。下面的不会与“here”和“hear”发生冲突,因为随着索引的增加,我会将每个字符与索引相乘。

        int keyHash(string key)
        {
            unsigned int k = (int)key.length();
            unsigned int u = 0,n = 0;
        
            for (Uint i=0; i<k; i++)
            {
                n = (int)key[i];
                u += i*n%31;
            }
            return u%139;
        }
        

        【讨论】:

          【解决方案12】:

          sdbm:此算法是为 sdbm(ndbm 的公共域重新实现)数据库库创建的

          static unsigned long sdbm(unsigned char *str)
          {   
              unsigned long hash = 0;
              int c;
              while (c = *str++)
                      hash = c + (hash << 6) + (hash << 16) - hash;
          
              return hash;
          }
          

          【讨论】:

            【解决方案13】:

            这是一个简单的散列函数,用于我构建的散列表。它基本上用于获取文本文件并将每个单词存储在代表字母顺序的索引中。

            int generatehashkey(const char *name)
            {
                    int x = tolower(name[0])- 97;
                    if (x < 0 || x > 25)
                       x = 26;
                    return x;
            }
            

            这基本上是根据单词的第一个字母对单词进行哈希处理。因此,以“a”开头的单词将获得 0 的哈希键,“b”将获得 1,依此类推,“z”将为 25。数字和符号的哈希键为 26。这提供了一个优势;您可以轻松快速地计算给定单词在哈希表中的索引位置,因为它全部按字母顺序排列,如下所示: 代码可以在这里找到:https://github.com/abhijitcpatil/general

            输入以下文本:有一天阿蒂克斯对杰姆说:“我宁愿你在后院朝锡罐开枪,但我知道你会去的 鸟之后。射击所有你想要的蓝鸟,如果你能击中它们,但是 记住杀死一只知更鸟是一种罪过。”那是我唯一一次 曾经听阿蒂克斯说做某事是一种罪过,我问小姐 莫迪关于它。 “你父亲是对的,”她说。 “知更鸟不 除了制作音乐让我们享受之外,只做一件事。他们不吃 人们的花园,不要在玉米床上筑巢,他们不做一件事 但为我们唱出他们的心声。这就是为什么杀死一个人是一种罪过 知更鸟。

            这将是输出:

            0 --> a a about asked and a Atticus a a all after at Atticus
            1 --> but but blue birds. but backyard
            2 --> cribs corn can cans
            3 --> do don’t don’t don’t do don’t do day
            4 --> eat enjoy. except ever
            5 --> for for father’s
            6 --> gardens go
            7 --> hearts heard hit
            8 --> it’s in it. I it I it’s if I in
            9 --> jays Jem
            10 --> kill kill know
            11 --> 
            12 --> mockingbird. music make Maudie Miss mockingbird.”
            13 --> nest
            14 --> out one one only one
            15 --> people’s
            16 --> 17 --> right remember rather
            18 --> sin sing said. she something sin say sin Shoot shot said
            19 --> to That’s their thing they They to thing to time the That to the the tin to
            20 --> us. up us
            21 --> 
            22 --> why was was want
            23 --> 
            24 --> you you you’ll you
            25 --> 
            26 --> “Mockingbirds ” “Your ‘em “I’d
            

            【讨论】:

            • 一个好的散列函数可以在桶中平均分配值。
            【解决方案14】:

            这将避免任何碰撞,并且在我们使用计算转移之前它会很快。

             int k = key.length();
                int sum = 0;
                for(int i = 0 ; i < k-1 ; i++){
                    sum += key.charAt(i)<<(5*i);
                }
            

            【讨论】:

              猜你喜欢
              • 2021-02-15
              • 2013-06-09
              • 2013-11-16
              • 2019-03-02
              • 2011-04-11
              • 2011-10-06
              • 2011-12-22
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多