2

我有一个类在集合和字典中被大量使用。出于性能原因,此类Hashable以旧方式实现并缓存计算的哈希:

let hashValue: Int

init(...) {
    self.hashValue = ...
}

在 Xcode 10.2 中,我看到一个警告,该警告已hashValue被弃用,很快将不再是协议要求。

困扰我的是无论如何都缺乏缓存计算哈希的能力,因为hash(into:)它不返回任何东西。

func hash(into hasher: inout Hasher) {
    hasher.combine(...)
}

在操场上考虑以下示例

class Class: Hashable {
    let param: Int

    init(param: Int) {
        self.param = param
    }

    static func ==(lhs: Class, rhs: Class) -> Bool {
        return lhs.param == rhs.param
    }

    public func hash(into hasher: inout Hasher) {
        print("in hash")
        hasher.combine(param)
    }
}

var dict = [Class: Int]()
let instance = Class(param: 1)
dict[instance] = 1
dict[instance] = 2

您将看到以下日志

in hash
in hash
in hash

我不知道,为什么我们看到 3 个调用而不是 2 个,但我们确实看到了 =)。

因此,每次您使用相同的实例作为字典键或将此实例添加到集合中时,您都会收到一个新hash(into:)调用。

在我的代码中,这样的开销被证明是非常昂贵的。有谁知道解决方法?

4

1 回答 1

5

一种选择是创建自己的Hasher,将实例的“基本组件”提供给它,然后调用finalize()以获取Int可以缓存的哈希值。

例如:

class C : Hashable {
  let param: Int

  private lazy var cachedHashValue: Int = {
    var hasher = Hasher()
    hasher.combine(param)
    // ... repeat for other "essential components"
    return hasher.finalize()
  }()

  init(param: Int) {
    self.param = param
  }

  static func ==(lhs: C, rhs: C) -> Bool {
    return lhs.param == rhs.param
  }

  public func hash(into hasher: inout Hasher) {
    hasher.combine(cachedHashValue)
  }
}

有几点需要注意:

  • 它依赖于您的“基本组件”是不可变的,否则需要根据突变计算新的哈希值。
  • 哈希值不能保证在程序的执行过程中保持稳定,所以不要序列化cachedHashValue

显然,在存储单个的情况下,Int这不会那么有效,但对于更昂贵的实例,这可能有助于提高性能。

于 2019-08-02T20:01:03.007 回答