仍然在可变对象的哈希码上

时间:2015-10-13 09:58:37

标签: hash hashtable hashcode

我知道,我知道,有很多关于哈希码的问题,但我想就一些计算可变对象哈希码的解决方案提出意见。

从这个假设开始(documentation):

  

通常,对于可变引用类型,只应在以下情况下覆盖GetHashCode:

     
      
  • 您可以从不可变的字段计算哈希码;或
  •   
  • 当对象包含在依赖于其哈希码的集合中时,您可以确保可变对象的哈希码不会更改。
  •   
     

否则,您可能会认为哈希表中的可变对象丢失了。

当我需要将可变对象存储到哈希表中时,哪个是最佳选择?

解决方案1 ​​

忽略此问题。计算是否使用其中一种可用算法(此处和C#中的地理坐标示例):

public override Int32 GetHashCode() {
    Int32 n1 = 99999997;
    Int32 hash_lat = this.Latitude.GetHashCode() % n1;
    Int32 hash_lng = this.Longitude.GetHashCode();
    _final_hashcode = (((hash_lat << 5) + hash_lat) ^ hash_lng);
    return _final_hashcode.Value;
}

解决方案2

第一次计算可变值并将其存储下次:

private Int32? _final_hashcode = null;
public override Int32 GetHashCode() {
    // hash code must not change when lat and lng does change
    if (_final_hashcode == null) {
        Int32 n1 = 99999997;
        Int32 hash_lat = this.Latitude.GetHashCode() % n1;
        Int32 hash_lng = this.Longitude.GetHashCode();
        _final_hashcode = (((hash_lat << 5) + hash_lat) ^ hash_lng);
    }
    return _final_hashcode.Value;
}

解决方案3

为仅用于哈希码的对象添加私有不可变密钥。这样,当可变字段发生变化时,哈希码不会改变。

这是一个使用随机生成的私有GUID的示例,该GUID不是该类所必需的,仅用于哈希代码:

public class GeoPosition {

    private const Guid _guidForHash = Guid.NewGuid(); // init during contruction

    public override Int32 GetHashCode() {
        return _guidForHash.GetHashCode();
    }

    // mutable properties here and other stuff
    // ...
}

你的意见是什么?

1 个答案:

答案 0 :(得分:0)

这很简单:

  • 解决方案2 :如果您有对象o1和o2并且它们具有不同的字段值,则它们具有不同的哈希码。如果您随后更改这些对象的字段以使它们彼此相等,则它们仍然不会具有相同的哈希码。它打破了约束:o1 == o2 implies hash(o1) == hash(o2)。不可行的解决方案。

  • 解决方案3 :与2相同的问题。

  • 解决方案1 ​​:正确的哈希函数,但每次都需要重新计算哈希码。

所以解决方案1 ​​就是这样。如果你需要优化它(记住过早优化是所有邪恶的根源),你可以缓存哈希码并在每个属性写入后更新它:

private Int32 UpdateHashCode() {
        Int32 n1 = 99999997;
        Int32 hash_lat = this.Latitude.GetHashCode() % n1;
        Int32 hash_lng = this.Longitude.GetHashCode();
        cached_hashcode = (((hash_lat << 5) + hash_lat) ^ hash_lng);
}
private Int32 cached_hashcode = null;
public override Int32 GetHashCode() {
    if (cached_hashcode == null) {
        UpdateHashCode();
    }
    return cached_hashcode.Value;
}
private string latitude;
public string Latitude {
    set {
        latitude = value;
        UpdateHashCode();
    }
}
private string longitude;
public string Longitude {
    set {
        longitude = value;
        UpdateHashCode();
    }
}