C语言基础 理解GetHashCode()的缺陷

C语言基础 理解GetHashCode()的缺陷,第1张

C语言基础 理解GetHashCode()的缺陷,第2张

承诺一个应该避免写的方法。GetHashCode()只在一个地方使用:在基于哈希结构的集合中,用来定义key(键值)的哈希值,一般是哈希表或者字典容器。因为基类在GetHashCode()的实现上有很多问题,所以只在一个地方用就好了。对于引用类型,这也可以,但是效率很低。对于值类型,基类的版本经常不正确,而且越来越差。完全可以不写GetHashCode(),这样既高效又正确。没有一个方法比GetHashCode()带来更多的讨论和困惑。继续读下去,消除所有的困惑。
如果您正在定义一个永远不会在容器内用作键的类型,这并不重要。表示WinForm控件、网页控件或数据库连接的类型,不太可能用作集合中的键。在这种情况下,什么也不要做。的所有引用类型都将有一个正确的哈希代码,即使它是低效的。值类型应该是不可变的。在这种情况下,默认的实现虽然效率不高,但是可以工作。在您创建的大多数类型中,方法是完全避免GetHashCode()的存在。
Examda提示:要创建一个用作哈希表键的类型,需要自己编写GetHashCode()的实现,所以继续阅读。基于哈希的容器使用哈希代码来优化搜索。每个对象生成一个称为哈希代码的整数值。存储在桶(容器,桶?)李。要搜索一个对象,您需要它的键值。在桶容器中搜索它。英寸Net中,每个对象都有一个由系统确定的哈希代码。Object.GetHashCode()。GetHashCode()的任何重载都必须遵循这三条规则:
1。如果两个对象相等(由= =运算符定义),它们必须生成相同的哈希值。否则,哈希值不能用于在容器中查找对象。
2。对于任何对象A,GetHashCode()必须是实例不变量。无论在中调用什么方法,GetHashCode()都必须始终返回相同的值。这可以确保放置在桶容器中的对象总是在正确的桶中。
3。哈希方法应该为所有输入生成一个整数范围内的随机分布。这就是为什么使用基于散列结构的容器是高效的。
编写一个正确有效的哈希方法需要更多这类知识,以确保符合规则3。系统中定义的版本。对象和系统。值类型没有这个优势。当您几乎不知道您的具体类型时,这些版本必须提供的默认行为。对象。GetHashCode()使用系统的内部字段。对象类来生成哈希值。每个对象在创建时都被赋予一个新的对象值(存储为整数值)。这些值从1开始,每次创建任何类型的新对象时,值都会增加。对象标识符字段设置在系统内部。对象构造函数,以后不能修改。对象。GetHashCode()返回对象标识符字段的哈希值作为结果哈希值。
现在检查对象。根据这三条规则获取HashCode()。如果两个对象相等,除非重写了= =运算符,否则对象。GetHashCode()将返回相同的哈希值。= =系统的版本检测对象标识符。Object GetHashCode()返回内部对象标识符字段,这是有效的。但是,如果您已经提供了自己版本的= =,则还必须提供自己版本的GetHashCode(),以确保符合第一条规则。第9项详细描述了平等。
遵循第二条规则:对象创建后,哈希代码永远不变。
第三条规则,所有的输入都要在整数范围内随机分布,这是不成立的。除非你创建了大量的对象,否则一个数字队列不会在整数范围内随机分布,而散列码由对象生成。GetHashCode()集中在整数范围的低端。
这意味着对象。GetHashCode()是正确的,但效率不高。如果你基于你定义的引用类型创建一个哈希表,默认行为继承自System。对象是一个工作缓慢的哈希表。当创建用作散列键值的引用类型时,应该重写GetHashCode(),以便在特定类型的整数范围内获得更好的散列值分布。
在讲述如何编写自己重写的GetHashCode版本之前,本节使用这三个相同的规则来检查值。GetHashCode()。系统。ValueType重写GetHashCode()为所有值类型提供默认行为。这个版本返回类型中定义的第一个字段的哈希值作为它自己的哈希值。考虑这个例子:
public struct my struct
{
private string msg;
private int 32 id;
私有日期时间纪元;
}
从MyStruct对象返回的哈希代码是msg字段生成的哈希代码。下面的代码片段总是返回true:
my structs = new my structs();
return s . get hashcode()= = s . msg . get hashcode();
翻译的时候,环境。Net2.0,实验:
总是返回false
第一条规则是2个相等的对象(等式由= =)必须有相同的哈希码。对于值类型,在大多数情况下都遵守此规则。但是你可以破坏它,就像你处理引用类型一样。ValueType = =()的运算符比较结构中多个字段中的第一个字段,这满足规则1。只要您定义了任何overridden = =操作符,您将使用第一个字段并且它将工作。任何结构,如果它的第一个字段不参与类型的相等,就会违反这个规则,破坏GetHashCode()。
第二条规则规定哈希代码必须是实例不变量。只有当这个结构中的第一个字段是不可变的,才能满足这个规则。如果第一个字段的值可以更改,那么哈希代码也可以更改,这违反了这个规则。是的,对于您创建的任何结构,如果第一个字段在其生命周期内可以被修改,那么GetHashCode()将被破坏。为什么不可变值类型是你的选择是另一个原因(见第17条)。
第三条规则取决于第一个字段的类型及其使用方式。如果第一个字段生成一个整数范围内的随机分布,并且它也将结构中的所有值展开,那么该结构也可以生成一个很好的平均分布。但是,如果第一个字段经常有相同的值,这个规则也会被打破。考虑对之前的结构做一个小的修改;
public struct my struct
{
private DateTime epoch;
私有字符串消息;
private int 32 id;
}
如果epoch字段设置为当前日期(不包括时间),则在特定日期创建的所有MyStruct对象将具有相同的哈希值。这阻止了所有哈希值的平均分布。
总结对象的默认行为。GetHashCode(),它在引用类型上可以正确工作,尽管它不需要生成有效的分布(如果您重写了Object.operator==(),它将破坏GetHashCode())。值类型。只有当结构中的第一个字段是只读的时,GetHashCode()才能工作。值类型。只有当结构满足以下条件时,GetHashCode()才能生成高效的哈希代码:在其输入中包含所有有意义集合的值。如果您计划构建一个更好的哈希代码,您需要对您的类型添加一些限制。重新检查这三条规则,这次是在构建GetHashCode()的工作实现的上下文中。首先,如果两个对象按照= =运算符的定义相等,则它们必须返回相同的哈希值,并且用于生成哈希代码的任何属性或数据值都必须参与该类型的相等判断。显然,这意味着用作等式的属性也用于生成哈希代码。也有可能某些属性参与相等判断,但不用于哈希代码计算。系统的默认行为。ValueType做到了这一点,但这意味着经常违反规则3,同一个数据元素应该同时参与两个计算。
第二条规则是GetHashCode()返回的值必须是实际变量。假设你定义了一个引用类型客户:
公共类客户
{
私有字符串名;
私人小数收入;
公共客户(字符串名称)
{
name = name;
}
公共字符串名称
{
get { return Name;}
set { name = value;}
}
公共重写int 32 get hashcode()
{
返回名称。get hashcode();
}
}
假设执行下面的代码片段:
customer C1 = new customer(" acme products ");
myHashMap。添加(c1,订单);
//哎呀,名字不对:
c1。Name = "Acme软件";
哈希映射中缺少C1。当您在图中放置c1时,哈希代码由字符串“Acme Products”生成。客户名称更改为“Acme Software”后,哈希代码值发生了变化。现在它由新名称“Acme Software”生成。C1存储在由“Acme产品”定义的桶容器中,但它应该存储在由“Acme软件”定义的桶容器中。您在自己的集合中失去了客户,因为哈希代码不是对象不变量。存储对象后,您已经修改了正确的桶容器。
只有当客户是参照类型时,才会出现前面的情况。值类型会做出不同的错误行为,但也会导致问题。如果Customer是值类型,则c1的副本将存储在哈希表中。修改name值的最后一行代码对存储在哈希表中的副本没有影响。因为装箱和解包都是复制的,所以在将值类型对象添加到集合中之后,很难修改它的成员。
表达规则2的方式是定义哈希代码方法,该方法将基于一个或多个不变属性返回值。系统。对象通过使用不可更改的对象标识符来遵守这一规则。系统。ValueType希望您的类型的第一个字段不会更改。没有比让你的类型不可变更好的方法了。当你定义一个值类型作为散列容器中的一个键时,它必须是一个不可变的类型。如果你违反了这个建议,你的类型的用户会找到一种方法来破坏使用你的类型作为键的哈希表。再看Customer类,可以修改使用户名不可变:
public class Customer
{
private readonly string name;
私人小数收入;
公共客户(字符串名称):this(name,0)
{
}
公共客户(字符串名称,小数收入)
{
name = name;
收入=收益;
}
公共字符串名称
{
get { return Name;}
}
//更改名称,返回新对象:
public Customer ChangeName(String newName)
{
返回新客户(newName,revenue);
}
公共重写int 32 get hashcode()
{
返回名称。get hashcode();
}
}
使名称不可变会改变你的以下行为:应该如何处理customer对象来修改名称:
customer C1 = new customer(" acme products ");
myHashMap。添加(c1,订单);
//哎呀,名字错了:
客户c2 = c1。ChangeName(“Acme软件”);
Order o = my hashmap[C1]as Order;
myHashMap。删除(C1);
myHashMap。Add(c2,o);
您必须删除原来的客户,修改名称并将新的客户对象添加到哈希表中。它看起来比第一个版本更笨重,但它的工作。以前的版本允许程序员编写不正确的代码。通过强制用于计算哈希值的属性不可变,您可以获得正确的行为,并且您的用户类型不会出错。是的,这个版本效果更好。你在强迫开发人员编写更多的代码,但仅仅是因为这是编写正确代码的方法。请确保用于计算哈希值的任何数据成员都是不可变的。
第三个规则是GetHashCode()应该为所有输入生成一个整数范围内的随机分布。满足这一要求取决于您创建的类型的细节。如果有神奇的公式,它早就在系统中实现了。对象,则该子句将不存在。一个通用且成功的算法是:对一个类型中的所有字段使用GetHashCode()后,返回值为XOR。如果您的类型包含一些可变字段,请将它们从计算中排除。
GetHashCode()有非常特殊的要求:相等的对象必须产生相等的哈希码,哈希码必须对对象不可变,并且产生平均分布,才能获得效率。只有不可变值类型可以满足这三个规则。对于其他类型,取决于默认行为,但其缺陷要理解。

位律师回复
DABAN RP主题是一个优秀的主题,极致后台体验,无插件,集成会员系统
白度搜_经验知识百科全书 » C语言基础 理解GetHashCode()的缺陷

0条评论

发表评论

提供最优质的资源集合

立即查看 了解详情