根据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 方法感到困惑,我知道这用于避免生成冲突哈希码。但我有以下问题:
为什么参数位置设置为2?
为什么该方法首先右移(32位)然后左移位置,它是否具有特定含义?
如上所述,该方法用于减少碰撞的情况,例如, new Point(5,8)vs new Point(8,5),但如果我创建一个像新Point(3,16)这样的对象,它将获得与新Point(5,8)相同的哈希码,所以..这个方法的真正作用是什么?
答案 0 :(得分:3)
我不知道为什么他们选择了这个特定的哈希码实现,但关于这个问题:
- 为什么这个方法先做右移(32个位置)然后做左移位置,它有特定含义吗?
醇>
这里的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
值进行转换,我们可以使用哈希码,不仅要考虑x
和y
值,还要考虑它们的顺序,而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
方法将用于识别它们。