【问题标题】:C# HashCode BuilderC# 哈希码生成器
【发布时间】:2010-05-26 11:13:26
【问题描述】:

我以前经常使用 apache hashcode builder

这对于 C# 是否存在

【问题讨论】:

标签: c# java


【解决方案1】:

这是我自制的建造者。

用法:

hash = new HashCodeBuilder().
             Add(a).
             Add(b).
             Add(c).
             Add(d).
             GetHashCode();

a,b,cd 是什么类型的字段无关紧要,易于扩展,无需创建数组。

来源:

public sealed class HashCodeBuilder
{
    private int hash = 17;

    public HashCodeBuilder Add(int value)
    {
        unchecked
        {
            hash = hash * 31 + value; //see Effective Java for reasoning
             // can be any prime but hash * 31 can be opimised by VM to hash << 5 - hash
        }
        return this;
    }

    public HashCodeBuilder Add(object value)
    {
        return Add(value != null ? value.GetHashCode() : 0);
    }

    public HashCodeBuilder Add(float value)
    {
        return Add(value.GetHashCode());
    }

    public HashCodeBuilder Add(double value)
    {
        return Add(value.GetHashCode());
    }

    public override int GetHashCode()
    {
        return hash;
    }
}

示例用法:

public sealed class Point
{
    private readonly int _x;
    private readonly int _y;
    private readonly int _hash;

    public Point(int x, int y)
    {
        _x = x;
        _y = y;
        _hash = new HashCodeBuilder().
            Add(_x).
            Add(_y).
            GetHashCode();
    }

    public int X
    {
        get { return _x; }
    }

    public int Y
    {
        get { return _y; }
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as Point);
    }

    public bool Equals(Point other)
    {
        if (other == null) return false;
        return (other._x == _x) && (other._y == _y);
    }

    public override int GetHashCode()
    {
        return _hash;
    }
}

【讨论】:

  • 我喜欢封装这种可重用代码的概念。但是,由于方法调用,不会有性能损失吗?
  • @SteveB 取决于你如何使用它。鉴于,理想情况下,您应该只在哈希码中使用不可变数据,如果您在构造函数中这样做一次,然后将结果存储在私有成员 hash 中,这比每次调用 GetHashCode 时进行正常计算更有效。
  • @SteveB 添加了示例用法来说明我的意思。也有正确的 equals 实现。
【解决方案2】:

我使用以下:

public static int ComputeHashFrom(params object[] obj) {
    ulong res = 0;
    for(uint i=0;i<obj.Length;i++) {
        object val = obj[i];
        res += val == null ? i : (ulong)val.GetHashCode() * (1 + 2 * i);
    }
    return (int)(uint)(res ^ (res >> 32));
}

使用这样的助手快速、简单且可靠,但它有两个潜在的缺点(您不太可能经常遇到,但需要注意):

  • 它可以为某些参数分布生成较差的哈希码。例如,对于任何int xComputeHashFrom(x*-3, x) == 0 - 因此,如果您的对象具有某些病态属性,您可能会遇到许多哈希码冲突,从而导致字典和哈希集性能不佳。这不太可能发生,但类型感知的哈希码计算可以更轻松地避免此类问题。
  • 哈希码的计算比专门的计算要慢。特别是,它涉及分配 params 数组和一个循环 - 如果您只有两个成员要处理,这会产生相当多的不必要的开销。

这些缺点都不会导致任何错误,只是效率低下;并且两者都在分析器中显示为此方法或哈希码使用者内部的光点。

【讨论】:

  • 关于速度的警告,我要补充一点,您还可以使用类型感知方法生成更好的哈希码,特别是如果您对最常发生的值有一个合理的想法。我倾向于更关心计算速度。
  • 这种简单安全的方法的重点是简单安全。对于对象的某些分布,这将导致更差的哈希码,并且总是需要更多的时间来计算。然而,在一般情况下,这些哈希码可以正常工作(毕竟,组成的 obj 成员通常具有类型感知的 GetHashCode 实现),并且大多数程序不会花费大部分时间来制作或使用哈希码。但是,根据实际经验,我会注意到我遇到了 GetHashCode 性能问题,但没有遇到 this 实现的质量问题 - YMMV。
  • 在实践中,由于对象相等比较通常比.GetHashCode 便宜得多,因此如果遇到几个 冲突,您最终只需支付很少的费用。另一方面,如果您正在进行大量的集合/字典计算,您可以简单地缓存未更改对象的哈希码;但是您无法避免哈希码错误的后果。无论如何,在实践中我不会为任何更复杂的事情而烦恼,直到分析表明它是值得的——它几乎从来没有这样做过。
  • 是的,我同意。如前所述,我会在您对速度的警告中添加这件事,但这并不意味着我认为这不是一个合理的通用方法。
【解决方案3】:

C# 没有内置的 HashCode 构建器,但您可以自己构建。我最近遇到了这个精确的问题,并通过使用泛型创建了这个不使用装箱的哈希码生成器,并实现了修改后的FNV 算法来生成特定的哈希。但是您可以使用任何您喜欢的算法,例如 System.Security.Cryptography 中的算法之一。

    public static int GetHashCode<T>(params T[] args)
    {
        return args.GetArrayHashCode();
    }

    public static int GetArrayHashCode<T>(this T[] objects)
    {
        int[] data = new int[objects.Length];

        for (int i = 0; i < objects.Length; i++)
        {
            T obj = objects[i];
            data[i] = obj == null ? 1 : obj.GetHashCode();
        }

        return GetFnvHash(data);
    }

    private static int GetFnvHash(int[] data)
    {
        unchecked
        {
            const int p = 16777619;
            long hash = 2166136261;

            for (int i = 0; i < data.Length; i++)
            {
                hash = (hash ^ data[i]) * p;
            }

            hash += hash << 13;
            hash ^= hash >> 7;
            hash += hash << 3;
            hash ^= hash >> 17;
            hash += hash << 5;

            return (int)hash;
        }
    }

【讨论】:

    【解决方案4】:

    Microsoft 最近发布了一个计算哈希码的类。请参阅https://docs.microsoft.com/en-us/dotnet/api/system.hashcode。您需要在项目中包含 NuGet 包 Microsoft.Bcl.HashCode 才能使用它。

    使用示例:

    using System.Collections.Generic;
    
    public class MyClass {
        public int MyVar { get; }
        public string AnotherVar { get; }
        public object MoreVars;
     
        public override int GetHashCode() 
            => HashCode.Combine(MyVar, AnotherVar, MoreVars);
    }
    

    【讨论】:

      【解决方案5】:

      现在我利用 ValueTuples、ref Tuples 或匿名类型:

      var hash = (1, "seven").GetHashCode();
      var hash2 = Tuple.Create(1, "seven").GetHashCode();
      var hash3 = new { Number = 1, String = "seven" }.GetHashCode();
      

      我相信值元组会最快。

      【讨论】:

        猜你喜欢
        • 2020-12-21
        • 1970-01-01
        • 2013-04-11
        • 1970-01-01
        • 2016-02-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-12-31
        相关资源
        最近更新 更多