标准库中所有的基本数据类型都是遵守 Hashable
协议的,它们包括字符串,整数,浮点数以及布尔值。不带有关联值得枚举类型也会自动遵守 Hashable
。
Dictionary
要求它的Key类型需要遵守 Hashable
协议,如果你想要将自定义类型用作字典的键,那么你必须手动为你的类型添加 Hashable
并满足它,这需要你实现 hashValue
属性。另外,因为 Hashable
本身是对 Equatable
的扩展,因此还需要为你的类型重载 ==
运算符。 你的实现必须保证哈希不变原则:两个同样的实例(由你实现的 ==
定义相同),必须拥有同样的哈希值。不过反过来不必为真:两个相同的哈希值的实例不一定需要相等。
不同的哈希值的数量是有限制,然而很多可以被哈希的类型(比如字符串)的个数是无穷的。哈希值可能重复这一特性,意味着 Dictionary
必须能够处理哈希碰撞。
优秀哈希算法的第二个特质是它应该很快。如果你的 hashValue 实现要消耗太多时间,那么它很可能会拖慢你的程序,让你从字典的 O(1)特性中得到的好处损失殆尽。
写一个能同时做到这些要求的哈希算法并不容易。对于一些由本身就是 Hashable
的数据类型组成的类型来说,将成员的哈希值进行 “异或”(XOR) 运算往往是一个不错的起点:
//Hashable 要求
struct Person {
var name: String
var zipCode: Int
var birthday: Date
}
extension Person: Equatable {
static func ==(lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name
&& lhs.zipCode == rhs.zipCode
&& lhs.birthday == rhs.birthday
}
}
extension Person: Hashable {
var hashValue: Int {
return name.hashValue ^ zipCode.hashValue ^ birthday.hashValue
}
}
异或计算方法的一个限制是,这个操作本身是左右对称的(也就是说 a^b == b^a),对于某些数据的哈希计算,这有时会造成不必要的碰撞。你可以添加一个位旋转并混合使用它们来避免这个问题。
网友评论