【问题标题】:Still on hash codes of mutable objects仍然在可变对象的哈希码上
【发布时间】:2016-01-11 00:38:09
【问题描述】:

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

从这个假设开始(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 的示例,该类不需要,仅用于哈希码:

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
    // ...
}

你的意见是什么?

【问题讨论】:

  • 我不知道判断解决方案的所有相关标准——但第三个让我觉得最干净,即使它有一点开销。
  • 对我来说这听起来像是一个 XY 问题。你所有的解决方案都有问题。对于解决方案 1,很明显(你自己写的)。对于解决方案 2 和 3,具有相同数据的两个对象可能会产生不同的哈希码,具体取决于第一次计算哈希码的时间。所以:您需要更好地描述您的真正问题是什么。
  • @ThomasMueller 你是对的。无论如何,您从哪里得到关于具有相同数据和不同哈希码的两个对象是一个问题的信息?是哈希码计算的要求,还是什么?
  • @ThomasMueller 我没有具体问题。这是我每次创建类时都会遇到的一个普遍问题,我知道我将在哈希集和/或可排序列表中使用这些类
  • 在这种情况下...我建议不要使用可变对象作为映射的键。您的所有解决方案都将阻止在哈希表中找到您的对象,除非您传递与键完全相同的对象。

标签: hash hashtable hashcode


【解决方案1】:

这很简单:

  • 解决方案 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();
    }
}

【讨论】:

  • Tnx 为您的解决方案,但您如何解决“当对象包含在依赖于其哈希码的集合中时,可变对象的哈希码不得更改”这一事实?
猜你喜欢
  • 2021-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-02
  • 1970-01-01
  • 2013-12-11
相关资源
最近更新 更多