1. 引言
在 Go 語言中,map
是一種內置的資料型別,它提供了一種高效的方式來存盤和檢索資料,map
是一種無序的鍵值對集合,其中每個鍵與一個值相關聯,使用 map 資料結構可以快速地根據鍵找到對應的值,而無需遍歷整個集合,
在 Go 語言中,map
是一種內置的資料型別,可以通過以下方式宣告和初始化:
m := make(map[keyType]valueType)
在使用map
時,我們通常會使用基本資料型別作為鍵,然而,當我們需要將自定義的結構體作為鍵時,就需要考慮結構體中是否包含參考型別的欄位,參考型別是指存盤了資料的地址的型別,如指標、切片、字典和通道等,在Go
中,參考型別具有動態的特性,可能會被修改或指向新的資料,這就引發了一個問題:能否將包含參考型別的自定義結構體作為map
的鍵呢?
2. map的基本模型
了解能否將包含參考型別的自定義結構體作為map
的鍵這個問題,我們需要先了解下map
的基本模型,在Go語言中,map
是使用哈希表、實作的,哈希表是一種以鍵-值對形式存盤資料的資料結構,它通過使用哈希函式將鍵映射到哈希值,
哈希函式是用于將鍵映射到哈希值的演算法,它接受鍵作為輸入并生成一個固定長度的哈希值,Go語言的 map
使用了內部的哈希函式來計算鍵的哈希值,
而不同的key
通過哈希函式生成的哈希值可能是相同的,此時便發生了哈希沖突,哈希沖突指的是不同的鍵經過哈希函式計算后得到相同的哈希值,由于哈希函式的輸出空間遠遠小于鍵的輸入空間,哈希沖突是不可避免的,此時無法判斷該key
是當前哈希表中原本便已經存在的元素還是由于哈希沖突導致不同的鍵映射到同一個bucket, 此時便需要判斷這兩個key
是否相等,
因此,在map
中,作為map
中的key
,需要保證其支持對比操作的,能夠比較兩個key
是否相等,
3. map 鍵的要求
從上面map
基本的模型介紹中,我們了解到,map
中的Key需要支持哈希函式的計算,同時鍵的型別必須支持對比操作,
在map
中,計算key
的哈希值,是由默認哈希函式實作的,對于map
中的key
并沒有額外的要求,
在map
中,判斷兩個鍵是否相等是通過呼叫鍵型別的相等運算子(==
或!=
)來完成的,因此key
必須確保該型別支持 ==
操作,這個要求是由 map
的實作機制決定的,map
內部使用鍵的相等性來確定鍵的存盤位置和檢索值,如果鍵的型別不可比較,就無法進行相等性比較,從而導致無法準確地定位鍵和檢索值,
在 Go 中,基本資料型別(如整數、浮點數、字串)和一些內置型別都是可比較的,因此它們可以直接用作 map
的鍵,然而,自定義的結構體作為鍵時,需要確保結構體的所有欄位都是可比較的型別,如果結構體包含參考型別的欄位,那么該結構體就不能直接用作 map
的鍵,因為參考型別不具備簡單的相等性比較,
因此,假如map
中的鍵為自定義型別,同時包含參考欄位,此時將無法作為map
的鍵,會直接編譯失敗,代碼示例如下:
type Person struct {
Name string
Age int
address []Address
}
func main() {
// 這里會直接編譯不通過
m := make(map[Person]int)
}
其次還有一個例外,那便是自定義結構體中包含指標型別的欄位,此時其是支持==
操作的,但是其是使用指標地址來進行hash
計算以及相等性比較的,有可能我們理解是同一個key
,事實上從map
來看并不是,此時非常容易導致錯誤,示例如下:
type Person struct {
Name string
Age int
address *Address
}
func main(){
m := make(map[Person]int)
p1 := Person{Name: "Alice", Age: 30, address: &Address{city: "beijing"}}
p2 := Person{Name: "Alice", Age: 30, address: &Address{city: "beijing"}}
m[p1] = 1
m[p2] = 2
// 輸出1
fmt.Println(m[p1])
// 輸出2
fmt.Println(m[p2])
}
這里我們定義了一個Person
結構體,包含一個指標型別的欄位address
,創建了兩個物件p1
和p2
,在我們的理解中,其是同一個物件,事實上在map
中為兩個兩個互不相關的物件,主要原因都是使用地址來進行hash計算以及相等性比較的,
綜上所述,如果自定義結構體中包含參考型別的欄位(指標為特殊的參考型別),此時將不能作為map
型別的key
,
4. 為什么不抽取hashCode和equals方法介面,由用戶自行實作呢?
當前go
中map
中哈希值的計算,其提供了默認的哈希函式,不需要由用戶去實作;其次key
的相等性比較,是通過==
運算子來實作的,也不由用戶自定義比較函式,那我們就有一個疑問了,為什么不抽取hashCode和equals方法介面,由用戶來實作呢?
4.1 簡單性和性能角度
相等性比較在 Go
語言中使用 ==
運算子來實作,而哈希函式是由運行時庫提供的默認實作,這種設計選擇我理解可能基于以下幾個原因:
- 簡單性:對于默認哈希函式函式來說,其內置在語言中的,無需用戶額外的實作和配置,這簡化了 map 的使用,對于相等性比較操作,
==
運算子進行比較是一種直觀且簡單的方式,在語法上,==
運算子用于比較兩個值是否相等,這種語法的簡潔性使得代碼更易讀和理解, - 性能:默認的哈希函式是經過優化和測驗的,能夠在大多數情況下提供良好的性能,其次使用
==
來實作相等性比較,由于==
運算子是語言層面的原生操作,編譯器可以對其進行優化,從而提高代碼的執行效率,
4.2 key不可變的限制
map
鍵的不可變性也是一個考慮因素,基于==
來判斷物件是否相等,間接保證了鍵的不可變性,目前,==
已經支持了大部分型別的比較,只有自定義結構體中的參考型別欄位無法直接使用==
進行比較,如果鍵中不存在參考型別欄位,這意味著放入Map鍵的值在運行時不能發生變化,從而保證了鍵在運行時的不可變性,
如果key
沒有不可變的限制,那么之前存盤在 map
中的鍵值對可能會出現問題,因為在放置元素時,map
會根據鍵的當前值計算哈希值,并使用哈希值來查找對應的存盤位置,如果放在map
中的鍵的值發生了變化,此時計算出來的hash
值可能也發生變化,這意味資料放在了錯誤的位置,后續即使使用跟map
中的鍵的同一個值去查找資料,也可能查找不到資料,
下面展示一個簡單的代碼,來說明可變型別作為key
會導致的問題:
type Person struct {
Name string
Age int
SliceField []string
}
func main() {
person := Person{Name: "Alice", Age: 25, SliceField: []string{"A", "B"}}
// 假設Person可以作為鍵,事實上是不支持的
personMap := make(map[Person]string)
personMap[person] = "Value 1"
// 修改person中SliceField的值
person.SliceField[0] = "X"
// 嘗試通過相同的person查找值
fmt.Println(personMap[person]) // 輸出空字串,找不到對應的值
}
如果抽取equals
方法介面,由用戶自行實作,此時key
的不可變性就需要用戶實作,其次go
語言也需要增加一些檢測機制,這首先增加了用戶使用的負擔,這并不符合go
語言設計的哲學,
4.3 總結
綜上所述,基于簡單性、性能和語意一致性的考慮以及鍵的不可變性,Go語言選擇使用==
運算子進行鍵的比較,而將哈希函式作為運行時庫的默認實作,更加符合go
語言設計的哲學,
5. 總結
在 Go 語言中,map 是一種無序的鍵值對集合,它提供了高效的資料存盤和檢索機制,在使用 map 時,通常使用基本資料型別作為鍵,然而,當我們想要使用自定義結構體作為鍵時,需要考慮結構體中是否包含參考型別的欄位,
自定義結構體作為map
的鍵需要滿足一些要求,首先,鍵的型別必須是可比較的,也就是支持通過==
運算子進行相等性比較,在Go
中,基本資料型別和一些內置型別都滿足這個要求,但是,如果結構體中包含參考型別的欄位,那么該結構體就不能直接作為map
的鍵,因為參考型別不具備簡單的相等性比較,
因此總的來說,包含參考型別欄位的自定義結構體,是不能作為map
的key
的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/554258.html
標籤:其他
上一篇:p3 FileInputStream 和 FileOutputStream
下一篇:返回列表