• GetHashCode()方法解析


    GetHashCode方法引入的缘由

            用大神Jeffrey Richter的话说,FCL的设计者认为,如果能将任何对象的任何实例放到一个哈希表集合中,会带来很多好处。为此,System.Object提供了虚方法GetHashCode,他能获取任意对象的Int32哈希码。我想,这也是GetHashCode方法当时引入的缘由。

    Object.GetHashCode方法的实现

            我们在.NET Framework4.0平台下进行测试,从上面的“任何对象的任何实例”这句话我们隐约可以猜测,Object.GetHashCode方法应该是具有相同引用的两个对象实例有相同的HashCode,如果两个对象实例的值完全相同,但是不是指向同一个引用,.NET应该不能保证这两个对象具有相同的HashCode,其实对以上结论验证的最简单的方法就是看一下微软实现的源代码,但是可惜的是微软在Object类下面是这样写的:

    public virtual int GetHashCode()
    {
         return RuntimeHelpers.GetHashCode(this);
    }

    在RuntimeHelpers里并没有什么实现:

    [System.Security.SecuritySafeCritical]  // auto-generated
    [ResourceExposure(ResourceScope.None)]
    [MethodImplAttribute(MethodImplOptions.InternalCall)]
    public new static extern bool Equals(Object o1, Object o2);

    但是,我们可以写代码进行测试,我们自定义一个类HashCodeObj

    class HashCodeObj
    {
        internal Int32 value1 = 0;
    
        internal Int32 value2 = 0;
    
        internal String str1 = "test";
    }

    然后进行如下处理

    class Program
        {
            static void Main(string[] args)
            {
                HashCodeObj obj1 = new HashCodeObj();
                obj1.value1 = 11;
                obj1.value2 = 22;
                obj1.str1 = "str1";
    
                HashCodeObj obj2 = new HashCodeObj();
                obj2.value1 = 11;
                obj2.value2 = 22;
                obj2.str1 = "str1";
    
                //Int32 intHashCode1 = 11;
                //Int32 intHashCode2 = 11;
    
                Console.WriteLine("obj1 hashCode: " + obj1.GetHashCode());
    
                //obj1.value1 = 22;
                Console.WriteLine("obj2 hashCode: " + obj2.GetHashCode());
    
                //Console.WriteLine("intHashCode1 hashCode: " + intHashCode1.GetHashCode());
                //Console.WriteLine("intHashCode2 hashCode: " + intHashCode2.GetHashCode());
                Console.ReadKey();
            }

    obj1和obj2是两个不同的实例,但是值完全相同,但是结果两个实例的hashCode不同

    image

    然后我们在第一次打印出obj1的hashCode值后对其value1值进行修改,然后再次获取obj1的hashCode,代码如下:

    class Program
        {
            static void Main(string[] args)
            {
                HashCodeObj obj1 = new HashCodeObj();
                obj1.value1 = 11;
                obj1.value2 = 22;
                obj1.str1 = "str1";
    
                //HashCodeObj obj2 = new HashCodeObj();
                //obj2.value1 = 11;
                //obj2.value2 = 22;
                //obj2.str1 = "str1";
    
                //Int32 intHashCode1 = 11;
                //Int32 intHashCode2 = 11;
    
                Console.WriteLine("obj1 hashCode: " + obj1.GetHashCode());
    
                obj1.value1 = 22;
                Console.WriteLine("obj1 hashCode: " + obj1.GetHashCode());
    
                //Console.WriteLine("intHashCode1 hashCode: " + intHashCode1.GetHashCode());
                //Console.WriteLine("intHashCode2 hashCode: " + intHashCode2.GetHashCode());
                Console.ReadKey();
            }

    最后打印的结果显示hashCode是相同的

    image

    测试结果

            从上面的例子我们可以推测,虽然我们无法获取Object.GetHashCode方法的实现代码,但是我们可以知道Object.GetHashCode方法是根据当前对象实例的地址来计算的。(不知道Object.GetHashCode方法的实现对没有任何影响,我们只知道结果就可以了)。

            Jeffrey Richter在他们著作中曾写过:System.Object实现的GetHashCode方法对其派生类型以及类型中的字段一无所知。现在体会一下这句话还是很有道理的。

    进一步的体会

            虽然我们知道了Object.GetHashCode的规则,但是msdn上的一段话还是非常值得思考的,我直接就用中文将这段话写到下面(需要看英文的地址:https://msdn.microsoft.com/zh-cn/library/11tbk3h9%28v=vs.100%29.aspx?f=255&MSPPError=-2147217396

    Object.GetHashCode 和 RuntimeHelpers.GetHashCode 方法用于以下情况:
    
    1. Object.GetHashCode 在您关心对象值的情况中非常有用。 具有相同内容的两个字符串将为 Object.GetHashCode 返回相同的值。 
    
    2. RuntimeHelpers.GetHashCode 在您关心对象标识的情况中非常有用。 具有相同内容的两个字符串将为 RuntimeHelpers.GetHashCode 返回不同值,因为尽管其内容相同,但这两个字符串属不同的字符串对象。

            现在我们应该理解为什么Object.GetHashCode方法根据当前对象实例的地址来计算的了,因为他调用的是RuntimeHelpers.GetHashCode,其实微软本意是让Object.GetHashCode方法通过对象实例的字段值来计算的,这也就是我们需要重写Object.GetHashCode方法的原因。

    当前GetHashCode方法的现状

            在当前的.NET中只有两种数据类型,一种是引用类型一种是值类型,引用类型直接继承Object基类,所以也没什么好说的,自己新建的类重写GetHashCode即可,值类型是继承ValueType基类的,ValueType已经重写了GetHashCode方法,但是里面用到了反射,这种方式对性能是有影响的,所以即使是值类型,也建议重写GetHashCode方法。最后,列出Jeffrey Richter对重写GetHashCode方法的意见:

    1. 算法要提供良好的随机分布,是哈希表获得最佳性能。

    2. Object或者ValueType的GetHashCode方法不属于好性能哈希算法,尽量不要调用。

    3. 算法应该至少使用一个实例字段

    4. 理想情况下,算法使用的字段应该是不可变的,也就是说,字段应该在对象构造时初始化,在对象生存期内永不改变

    5. 算法应该尽可能快的执行

    6. 包含相同值的不同对象应返回相同的哈希码。

  • 相关阅读:
    一次摸鱼
    scenes
    mysql日志
    十万个为什么
    ss
    mysql之explain
    mysql之索引
    mysql1
    分页
    ajax分页
  • 原文地址:https://www.cnblogs.com/sauronKing/p/5946613.html
Copyright © 2020-2023  润新知