【问题标题】:Generate unique ID from string in C#从 C# 中的字符串生成唯一 ID
【发布时间】:2021-06-17 03:11:51
【问题描述】:

我需要我的应用程序处理来自数据库的模组列表和本地下载的模组列表。 数据库的每个模组都有一个唯一的uint ID,我用它来识别他,但本地模组没有任何 ID。

起初我尝试使用模组的名称生成带有string.GetHashCode() 的 ID,但 GetHashCode 在应用程序的每次运行时仍然是随机的。 有没有其他方法可以从 mod 的名称生成一个持久的 uint ID ?

当前代码:

foreach(string mod in localMods)
{
    //This way I get a number between 0 and 2147483648
    uint newId = Convert.ToUInt32(Math.Abs(mod.GetHashCode());
    ProfileMod newMod = new ProfileMod(newId);
}

【问题讨论】:

  • 使用任何你喜欢的散列函数(MD5 等)。但请注意,可能会发生碰撞。
  • "GetHashCode 在每次运行应用程序时仍然是随机的" .不,那你基本上有一个严重的代码问题。 HashCodes 不应在应用程序运行之间更改。
  • @TomTom 实际上,object.GetHashCode() 的文档明确指出 In some cases, hash codes may be computed on a per-process or per-application domain basis. 因此调用 object.GetHashCode() 返回的值很可能在运行之间发生变化。
  • 碰撞应该不是真正的问题,因为它只处理少量的模组,甚至更少量的本地模组。 @TomTom 我也是这么想的......我使用的是Convert.ToUInt32(Math.Abs(mod.GetHashCode()),在两次运行之间,有时会有所不同
  • 在您的mod.GetHashCode() 示例中,mod 的类型是什么? string.GetHashCode() 通常确实在运行之间返回相同的值(但不能保证这样做,你绝不能依赖它)

标签: c# .net


【解决方案1】:

GetHashCode() 方法不会为相同的字符串返回相同的值,尤其是在您重新运行应用程序时。它有一个不同的目的(例如在运行时检查相等性等)。
所以,它shouldn't be used as a unique identifier

如果您想计算哈希并获得一致的结果,您可以考虑使用标准哈希算法,如 MD5、SHA256 等。 这是一个计算 SHA256 的示例:

using System;
using System.Security.Cryptography;
using System.Text;

public class Program
{
    public static void Main()
    {
        string input = "Hello World!";
        // Using the SHA256 algorithm for the hash.
        // NOTE: You can replace it with any other algorithm (e.g. MD5) if you need.
        using (var hashAlgorithm = SHA256.Create())
        {
            // Convert the input string to a byte array and compute the hash.
            byte[] data = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(input));

            // Create a new Stringbuilder to collect the bytes
            // and create a string.
            var sBuilder = new StringBuilder();

            // Loop through each byte of the hashed data
            // and format each one as a hexadecimal string.
            for (int i = 0; i < data.Length; i++)
            {
                sBuilder.Append(data[i].ToString("x2"));
            }

            // Return the hexadecimal string.
            var hash = sBuilder.ToString();

            Console.WriteLine($"The SHA256 hash of {input} is: {hash}.");
        }
    }
}

虽然 SHA256 产生的结果比 MD5 长,但发生冲突的风险要低得多。但是如果你仍然想要更小的哈希值(有更高的冲突风险),你可以使用 MD5,甚至 CRC32。

附:示例代码基于Microsoft's documentation 中的代码。

【讨论】:

    【解决方案2】:

    所以我最终听取了您的建议,并在另一篇文章中使用 SHA-1 找到了一个很好的答案

    private System.Security.Cryptography.SHA1 hash = new System.Security.Cryptography.SHA1CryptoServiceProvider();
    
    private uint GetUInt32HashCode(string strText)
    {
        if (string.IsNullOrEmpty(strText)) return 0;
        
        //Unicode Encode Covering all characterset
        byte[] byteContents   = Encoding.Unicode.GetBytes(strText);
        byte[] hashText       = hash.ComputeHash(byteContents);
        uint   hashCodeStart  = BitConverter.ToUInt32(hashText, 0);
        uint   hashCodeMedium = BitConverter.ToUInt32(hashText, 8);
        uint   hashCodeEnd    = BitConverter.ToUInt32(hashText, 16);
        var    hashCode       = hashCodeStart ^ hashCodeMedium ^ hashCodeEnd;
        return uint.MaxValue - hashCode;
    } 
    
    

    可能会被优化,但现在已经足够了。

    【讨论】:

    • 我认为没有必要对不同的部分进行异或。一个好的散列函数应该将熵均匀地分布在输出上,所以只取前 4 个字节就足够了。还要记住,即使是最好的散列函数也有相当高的与 32 位输出值发生冲突的风险。
    • 是的,将 uint 作为最终结果在碰撞方面是有风险的。此外,可能需要重新访问对哈希的各个部分进行异或运算,因为通过数学运算可能会发现最后一个操作会导致更多的冲突。 @JonasH,一般来说,如果你存储整个哈希(在字符串或字节数组中)而不是 uint 会更好。
    • @Just Shadow,显然保留整个哈希是最好的,但由于其他原因可能会很困难。因此,在易用性和避免碰撞之间存在权衡。我不知道确切的问题域足以判断什么更重要。
    • 好的。我将修改代码,但到目前为止,uint 的哈希是我唯一的解决方案。用户往往拥有最多 200 个项目,因此冲突并不常见。此外,到目前为止,所有数据库 ID 都在 0 到 2000000 之间。我还有一些错误的余地
    【解决方案3】:

    我不会相信任何涉及散列等的解决方案。最终,您最终会在 ID 中遇到冲突,尤其是当您的数据库中有大量记录时。

    我更愿意做的是在读取数据库时将其 int ID 转换为字符串,然后使用 Guid.NewGuid().ToString() 之类的函数为本地生成字符串 UID。

    这样你就不会有任何冲突了。

    我猜你将不得不采用某种这样的策略。

    【讨论】:

    • 问题是,我需要一个特定的 uint 来处理东西。否则,API 将不再工作
    • 然后,我要做的是设置我的本地 UInt 从 UInt32.MaxValue 开始,并且对于每个新的本地 UInt 将值减少 1。当然希望数据库中的那些不超过该值UInt32.MaxValue / 2 因为那里可能存在冲突。
    猜你喜欢
    • 2011-01-12
    • 1970-01-01
    • 2021-07-08
    • 2013-02-09
    • 1970-01-01
    • 1970-01-01
    • 2018-12-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多