【问题标题】:A fast hash function for string in C#C#中字符串的快速哈希函数
【发布时间】:2012-03-21 16:24:25
【问题描述】:

我想对一个长度最大为 30 的字符串进行哈希处理。如果我关心的是时间,那么最好的办法是什么。该函数将被调用超过 1 亿次。目前我正在使用以下代码,

static UInt64 CalculateHash(string read, bool lowTolerance)
{
    UInt64 hashedValue = 0;
    int i = 0;
    while (i < read.Length)
    {
        hashedValue += read.ElementAt(i) * (UInt64)Math.Pow(31, i);
        if (lowTolerance) i += 2;
        else i++;
    }
    return hashedValue;
}

【问题讨论】:

  • Object.GetHashCode() 方法对您不起作用是否有原因?您似乎在重新实现相同的概念。
  • 任何不使用浮点数学的东西都会更快。
  • GetHashCode 是不可持久的,所以如果他需要将 Hash Code 存储到数据库中,它是没有用的。再说一次,这也不是。你的用途是什么?您只需要在运行时对字符串进行哈希处理,或者您需要对哈希做什么?如果您需要存储 Adler-32 并且不会遇到太多冲突,则可能是一种选择。
  • @Pbasak 然后将其转换为uint 或使用0x7FFFFF 屏蔽它。
  • 运行分析器。这会告诉你慢的部分是什么。然后修复缓慢的部分

标签: c# string performance hash


【解决方案1】:

为了加快您的实施,(UInt64)Math.Pow(31, i) 调用应替换为查找:预先计算 31 的前 30 个幂的表,并在运行时使用它。由于长度限制为 30,因此您只需要 31 个元素:

private static unsigned long[] Pow31 = new unsigned long[31];

static HashCalc() {
    Pow31[0] = 1;
    for (int i = 1 ; i != Pow31.Length ; i++) {
        Pow31[i] = 31*Pow31[i-1];
    }
}

// In your hash function...
hashedValue += read.ElementAt(i) * Pow31[i];

【讨论】:

  • 我不太确定查表是否比整数乘法快。
  • @CodeInChaos 肯定比Math.Pow(31, i) 快​​。此外,当 i 在条件内上升 2 时,我还需要一个额外的乘法,所以我会先尝试查找。
【解决方案2】:
static UInt64 CalculateHash(string read)
{
    UInt64 hashedValue = 3074457345618258791ul;
    for(int i=0; i<read.Length; i++)
    {
        hashedValue += read[i];
        hashedValue *= 3074457345618258799ul;
    }
    return hashedValue;
}

这是一个 Knuth 哈希。你也可以使用Jenkins

【讨论】:

  • 根据我自己的测试,这个功能没有实现雪崩。 YMMV。
  • 情况更糟。但我应该量化我的原始陈述。在输入上切换单个位会导致大约 49.40% 的输出位切换(使用您的原始常量),这比基于 Bernstein 的函数要好得多。对于大多数用途来说,这可能已经足够了。但是,例如,SuperFastHash (landman-code.blogspot.com/2009/02/…) 给了我 50.02%。同一页上的 Murmur2 给了我 50.04%。
  • 它不适用于您关心的应用程序。它只是用于在哈希表中分配字符串。
  • 能否请您提供此算法的引用?我搜索了 TAOCP Vol 3,但找不到您拥有的这些常量。
  • @ShitalShah 我很确定它来自 TAOCP,但我不确定是哪一卷。
【解决方案3】:

首先,考虑使用GetHashCode()

对现有实现的简单改进:

static UInt64 CalculateHash(string read, bool lowTolerance)
{
    UInt64 hashedValue = 0;
    int i = 0;
    ulong multiplier = 1;
    while (i < read.Length)
    {
        hashedValue += read[i] * multiplier;
        multiplier *= 37;
        if (lowTolerance) i += 2;
        else i++;
    }
    return hashedValue;
}

它避免了昂贵的浮点计算,以及ElementAt的开销。

顺便说一句,(UInt64)Math.Pow(31, i) 不适用于较长的字符串。浮点数四舍五入将导致超过 15 个字符的乘数为 0。

【讨论】:

  • 乘数必须从大于 256 的素数开始,否则如果第一个字节很小,这会严重中断。
  • @DavidSchwartz 更大的素数当然更好,但可怕地打破有点夸大其词。
  • 如果一个 64 位散列函数有大量 2 字节输入发生冲突,IMO 会严重崩溃。 (但考虑到 OP 开始的功能有多糟糕,也许我的标准太高了。)
  • 即使素数 >256 但
  • 对所有使用 .NET Core 的人发出警告:在 .NET Core 中 GetHashCode 在应用程序重启之间随机化!这意味着每次重新启动/回收应用程序时,您都会为相同的字符串获得不同的哈希
猜你喜欢
  • 1970-01-01
  • 2014-03-26
  • 1970-01-01
  • 2013-03-13
  • 1970-01-01
  • 2013-09-12
  • 2015-03-09
相关资源
最近更新 更多