为什么GetHashCode方法需要在C#中进行转换

时间:2017-01-09 06:08:07

标签: c# gethashcode

根据MSDN GetHashCode方法:

public struct Point
{
    private int x;
    private int y;

    public Point(int x, int y)
    {
       this.x = x;
       this.y = y;
    }

    public override bool Equals(Object obj)
    {
       if (!(obj is Point)) return false;

       Point p = (Point) obj;
       return x == p.x & y == p.y;
    }

    public override int GetHashCode()
    { 
        return ShiftAndWrap(x.GetHashCode(), 2) ^ y.GetHashCode();
    } 

    private int ShiftAndWrap(int value, int positions)
    {
        positions = positions & 0x1F;

        // Save the existing bit pattern, but interpret it as an unsigned integer.
        uint number = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
        // Preserve the bits to be discarded.
        uint wrapped = number >> (32 - positions);
        // Shift and wrap the discarded bits.
        return BitConverter.ToInt32(BitConverter.GetBytes((number << positions) | wrapped), 0);
    }
}

我对 ShiftAndWrap 方法感到困惑,我知道这用于避免生成冲突哈希码。但我有以下问题:

  1. 为什么参数位置设置为2?

  2. 为什么该方法首先右移(32位)然后左移位置,它是否具有特定含义?

  3. 如上所述,该方法用于减少碰撞的情况,例如, new Point(5,8)vs new Point(8,5),但如果我创建一个像新Point(3,16)这样的对象,它将获得与新Point(5,8)相同的哈希码,所以..这个方法的真正作用是什么?

2 个答案:

答案 0 :(得分:3)

我不知道为什么他们选择了这个特定的哈希码实现,但关于这个问题:

  
      
  1. 为什么这个方法先做右移(32个位置)然后做左移位置,它有特定含义吗?
  2.   

这里的ShiftAndWrap()方法是一种算法的通用实现,用于将值左移N位并将溢出包装回结尾。所以在他们进行转换之前,他们首先得到最左边的N位,然后他们可以将它们追加到最后。

因此,如果我们只使用8位值(ShiftAndWrap() s)并使用byte =(二进制)11010010和{{value调用它,那么调用positions会是什么样子? 1}} = 3:

value = 11010010

positions = 3

wrapped = value >> (8 - positions)
        = 11010010 >> (8 - 3) 
        = 11010010 >> 5 
        = 00000110

result = value << positions | wrapped
       = 11010010 << 3 | 00000110 
       = 10010000 | 00000110 
       = 10010110

我们可以看到返回值10010110是将11010010移位三位并绕过结果的结果。

关于为什么他们不仅仅使用x ^ y的问题,我怀疑这是因为这意味着Point(N, M)将始终产生与Point(M, N)相同的哈希码。通过对x值进行转换,我们可以使用哈希码,不仅要考虑xy值,还要考虑它们的顺序,而x ^ y会忽略他们的订单。

在对包含相同类型的子组件的数据结构进行散列处理时,通常让散列函数以不同方式处理每个子组件,以使其位置重要。例如,Java对字符串使用此哈希公式(此处^表示指数,而不是XOR):

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

我们可以看到每个字符乘以31的不同幂,因此stop具有与pots不同的哈希码。

至于为什么他们选择2作为要转移的职位数量,这可能是任意的,或者他们可能已经做了一些评估,看看哪种程度的转变可能产生最佳分配。

答案 1 :(得分:0)

HashCode的要点是创建一个分布,以便数据结构可以将数据分配到某些桶中。它并不意味着平等。

如果查看HashSet的内部结构,可以看到该类使用HashCode来标识正确的存储桶,然后使用Equals方法确定相等性。

    /// <summary>
    /// Checks if this hashset contains the item
    /// </summary>
    /// <param name="item">item to check for containment</param>
    /// <returns>true if item contained; false if not</returns>
    public bool Contains(T item) {
        if (m_buckets != null) {
            int hashCode = InternalGetHashCode(item);
            // see note at "HashSet" level describing why "- 1" appears in for loop
            for (int i = m_buckets[hashCode % m_buckets.Length] - 1; i >= 0; i = m_slots[i].next) {
                if (m_slots[i].hashCode == hashCode && m_comparer.Equals(m_slots[i].value, item)) {
                    return true;
                }
            }
        }
        // either m_buckets is null or wasn't found
        return false;
    }

所以碰撞很好,它只是在那里,所以确保相对平等的分布,以便更快地识别和检索。这意味着,在您的情况下,这两个点都将放在同一个存储桶中,但它们的Equals方法将用于识别它们。