.NET中IEqualityComparer <T>中GetHashCode的作用是什么?


142

我试图了解IEqualityComparer接口的GetHashCode方法的作用。

以下示例来自MSDN:

using System;
using System.Collections.Generic;
class Example {
    static void Main() {
        try {

            BoxEqualityComparer boxEqC = new BoxEqualityComparer();

            Dictionary<Box, String> boxes = new Dictionary<Box,
                                                string>(boxEqC);

            Box redBox = new Box(4, 3, 4);
            Box blueBox = new Box(4, 3, 4);

            boxes.Add(redBox, "red");
            boxes.Add(blueBox, "blue");

            Console.WriteLine(redBox.GetHashCode());
            Console.WriteLine(blueBox.GetHashCode());
        }
        catch (ArgumentException argEx) {

            Console.WriteLine(argEx.Message);
        }
    }
}

public class Box {
    public Box(int h, int l, int w) {
        this.Height = h;
        this.Length = l;
        this.Width = w;
    }
    public int Height { get; set; }
    public int Length { get; set; }
    public int Width { get; set; }
}

class BoxEqualityComparer : IEqualityComparer<Box> {

    public bool Equals(Box b1, Box b2) {
        if (b1.Height == b2.Height & b1.Length == b2.Length
                            & b1.Width == b2.Width) {
            return true;
        }
        else {
            return false;
        }
    }

    public int GetHashCode(Box bx) {
        int hCode = bx.Height ^ bx.Length ^ bx.Width;
        return hCode.GetHashCode();
    }
}

Equals方法的实现是否足以比较两个Box对象?那就是我们告诉框架用来比较对象的规则的地方。为什么需要GetHashCode?

谢谢。

露西安


阅读:en.wikipedia.org/wiki/Hash_table,然后查看您是否更好地理解GetHashCode的目的。
支出者

1
看到这个好答案:stackoverflow.com/a/3719802/136967
Mikhail

Answers:


200

首先有一点背景...

.NET中的每个对象都有一个Equals方法和GetHashCode方法。

Equals方法用于将一个对象与另一个对象进行比较-查看两个对象是否等效。

GetHashCode方法生成对象的32位整数表示。由于一个对象可以包含多少信息没有限制,所以某些哈希码可以由多个对象共享-因此哈希码不一定是唯一的。

字典是一个非常酷的数据结构,它以较高的内存占用量为代价来换取(或多或少)恒定的添加/删除/获取操作成本。不过,这是一个不佳的选择。在内部,字典包含一个存储桶数组,可以在其中存储值。将键和值添加到字典时,将在键上调用GetHashCode方法。返回的哈希码用于确定应在其中存储键/值对的存储桶的索引。

当您要访问值时,请再次输入密钥。在Key上调用GetHashCode方法,并找到包含Value的存储桶。

将IEqualityComparer传递给字典的构造函数时,将使用IEqualityComparer.Equals和IEqualityComparer.GetHashCode方法代替Key对象上的方法。

现在,解释为什么两种方法都是必需的,请考虑以下示例:

BoxEqualityComparer boxEqC = new BoxEqualityComparer(); 

Dictionary<Box, String> boxes = new Dictionary<Box, string>(boxEqC); 

Box redBox = new Box(100, 100, 25);
Box blueBox = new Box(1000, 1000, 25);

boxes.Add(redBox, "red"); 
boxes.Add(blueBox, "blue"); 

在您的示例中使用BoxEqualityComparer.GetHashCode方法,这两个框都具有相同的哈希码-100 ^ 100 ^ 25 = 1000 ^ 1000 ^ 25 = 25-即使它们显然不是同一对象。在这种情况下它们是相同的哈希码的原因是,因为您使用的是^(按位异或)运算符,所以100 ^ 100会抵消掉剩下的零,就像1000 ^ 1000一样。当两个不同的对象具有相同的键时,我们称其为碰撞。

当我们将两个具有相同哈希码的键/值对添加到字典中时,它们都存储在同一存储桶中。因此,当我们想检索一个值时,将在我们的Key上调用GetHashCode方法以定位存储桶。由于存储桶中有多个值,因此字典会在存储桶中的所有“键/值”对上进行迭代,从而调用“键”上的“等于”方法以找到正确的值。

在您发布的示例中,两个框是等效的,因此Equals方法返回true。在这种情况下,字典具有两个相同的键,因此将引发异常。

TLDR

因此,总而言之,GetHashCode方法用于生成存储对象的地址。因此,字典无需搜索。它只是计算哈希码并跳转到该位置。Equals方法是对相等性的更好测试,但不能用于将对象映射到地址空间。


4
对于那些想知道^-运算符是什么的人,这是按位异或运算符,请参阅msdn.microsoft.com/en-us/library/zkacc7k1.aspx
R. Schreurs

2
只是要明确指出这一点:(msdn.microsoft.com/en-us/library/ms132155.aspx)对实现者的说明需要实现以确保如果Equals方法对两个对象x和y返回true,则返回值由GetHashCode方法获得的x必须等于为y返回的值。
Diego Frehner

2
@DiegoFrehner-你说得对。可能使人绊倒的另一件事是,如果修改了对象,则GetHashCode方法的值不应改变。因此,GetHashCode所依赖的对象中的字段应为只读(不可变)。这里有一个解释:stackoverflow.com/a/4868940/469701
sheikhjabootie

1
@Acentric:除非以影响相等性的方式对其进行了变异,否则对象的哈希码不应更改。如果可以以影响相等性的方式对类进行变异,则代码应避免在字典中存储任何可能暴露于在字典中对其进行变异的代码的实例。如果存储对象的代码遵守该规则,则具有反映可变状态的哈希码可能会很有用。太糟糕了。.NET不能更好地区分状态相等和等效,因为两者都是有用的概念。
2014年

3
@Acentric:即使不使用哈希码进行哈希表寻址,哈希码背后的基本思想是,两个对象具有不同的哈希码的知识意味着它们是不相等的,无需进行比较。因此,知道许多对象的哈希码与给定对象的哈希码不匹配,这意味着它们都不等于该对象。使用哈希码进行寻址基本上是一种忽略具有不同哈希码的对象的方法。
2014年

9

GetHashCode用于Dictionary集合,它创建哈希以在其中存储对象。这是一篇很好的文章,为什么以及如何使用IEqualtyComparerGetHashCode http://dotnetperls.com/iequalitycomparer


4
更多:如果需要比较Equals,则为enouf,但是当您需要从Dictionary中获取元素时,通过哈希而不是使用Equals可以更轻松地实现。
Ash

5

虽然有可能对每个存储的键调用Dictionary<TKey,TValue>GetValue和类似的方法Equals以查看其是否与所寻求的键相匹配,但这将非常慢。相反,像许多基于散列的集合一样,它依赖于GetHashCode快速排除大多数不匹配的值。如果调用GetHashCode一个正在寻找的项目会产生42,而一个集合有53,917个项目,但是调用GetHashCode了53,914个项目所产生的值不是42,那么只需要将3个项目与正在寻找的项目进行比较即可。其他53,914个可以安全地忽略。

GetHashCode包含在中的原因IEqualityComparer<T>是为了允许字典的使用者可能希望将其视为相等的对象,而这些对象通常不会将彼此视为相等。最常见的示例是一个调用者,该调用者希望使用字符串作为键,但使用不区分大小写的比较。为了使该工作高效地进行,字典将需要具有某种形式的哈希函数,该函数将为“ Fox”和“ FOX”产生相同的值,但希望为“ box”或“ zebra”产生其他值。由于GetHashCode内置方法String无法正常工作,因此字典需要从其他地方获取这种方法,IEqualityComparer<T>Equals 认为“ Fox”和“ FOX”彼此相同,但与“ box”或“ zebra”不同的方法。


正确回答问题!GetHashCode()必须为所讨论的对象补充Equals()。
Sumith

@Sumith:关于散列的许多讨论都谈到了存储桶,但是我认为考虑排除更为有用。如果比较昂贵,那么即使使用未按存储桶分类的集合,哈希也可以带来好处。
超级猫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.