標(biāo)準(zhǔn)庫(kù)中所有的基本數(shù)據(jù)類型都是遵守 Hashable
協(xié)議的,它們包括字符串,整數(shù),浮點(diǎn)數(shù)以及布爾值。不帶有關(guān)聯(lián)值得枚舉類型也會(huì)自動(dòng)遵守 Hashable
。
Dictionary
要求它的Key類型需要遵守 Hashable
協(xié)議,如果你想要將自定義類型用作字典的鍵,那么你必須手動(dòng)為你的類型添加 Hashable
并滿足它,這需要你實(shí)現(xiàn) hashValue
屬性。另外,因?yàn)?Hashable
本身是對(duì) Equatable
的擴(kuò)展,因此還需要為你的類型重載 ==
運(yùn)算符。 你的實(shí)現(xiàn)必須保證哈希不變?cè)瓌t:兩個(gè)同樣的實(shí)例(由你實(shí)現(xiàn)的 ==
定義相同),必須擁有同樣的哈希值。不過(guò)反過(guò)來(lái)不必為真:兩個(gè)相同的哈希值的實(shí)例不一定需要相等。
不同的哈希值的數(shù)量是有限制,然而很多可以被哈希的類型(比如字符串)的個(gè)數(shù)是無(wú)窮的。哈希值可能重復(fù)這一特性,意味著 Dictionary
必須能夠處理哈希碰撞。
優(yōu)秀哈希算法的第二個(gè)特質(zhì)是它應(yīng)該很快。如果你的 hashValue 實(shí)現(xiàn)要消耗太多時(shí)間,那么它很可能會(huì)拖慢你的程序,讓你從字典的 O(1)特性中得到的好處損失殆盡。
寫一個(gè)能同時(shí)做到這些要求的哈希算法并不容易。對(duì)于一些由本身就是 Hashable
的數(shù)據(jù)類型組成的類型來(lái)說(shuō),將成員的哈希值進(jìn)行 “異或”(XOR) 運(yùn)算往往是一個(gè)不錯(cuò)的起點(diǎn):
//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
}
}
異或計(jì)算方法的一個(gè)限制是,這個(gè)操作本身是左右對(duì)稱的(也就是說(shuō) a^b == b^a),對(duì)于某些數(shù)據(jù)的哈希計(jì)算,這有時(shí)會(huì)造成不必要的碰撞。你可以添加一個(gè)位旋轉(zhuǎn)并混合使用它們來(lái)避免這個(gè)問(wèn)題。