Go 1.14 相比 Go 1.13 有哪些值得注意的改動?
Go 1.14 值得關(guān)注的改動:
- 接口嵌入 :允許嵌入方法集重疊(方法名和簽名相同)的接口,解決了先前版本中存在的限制,特別是 接口菱形嵌入(diamond-shaped embedding graphs) 問題。
- 模塊與 Vendoring :當(dāng)存在 vendor 目錄且 go.mod 指定 Go 1.14 或更高版本時,go 命令默認(rèn)啟用 vendor 模式;同時,模塊下載支持了 Subversion,并改進(jìn)了代理錯誤信息的顯示。
- 運行時改進(jìn) :defer 的性能開銷大幅降低接近于零;Goroutine 實現(xiàn) 異步搶占式調(diào)度(asynchronously preemptible),解決了某些循環(huán)導(dǎo)致調(diào)度器阻塞或 GC 延遲的問題(盡管這可能導(dǎo)致 Unix 系統(tǒng)上出現(xiàn)更多 EINTR 錯誤);頁面分配器和內(nèi)部計時器效率也得到了提升。
- Go Modules 行為變更 :在顯式啟用模塊感知模式(GO111MODULE=on)但無 go.mod 文件時,多數(shù)命令功能受限;對于包含 go.mod 文件的模塊,go get 默認(rèn)不再自動升級到不兼容的主版本。
- 新增 hash/maphash 包 :提供對字節(jié)序列的高性能、非加密安全的哈希函數(shù),適用于哈希表等場景,其哈希結(jié)果在單進(jìn)程內(nèi)一致,跨進(jìn)程則不同。
下面是一些值得展開的討論:
接口嵌入允許方法集重疊
Go 1.14 根據(jù) overlapping interfaces proposal[1],放寬了接口嵌入的限制。現(xiàn)在允許一個接口嵌入多個其他接口,即使這些被嵌入的接口包含了方法名和方法簽名完全相同的方法。
這一改動主要解決了先前版本中存在的一個問題,尤其是在接口構(gòu)成 接口菱形嵌入(diamond-shaped embedding graphs) 的場景下。
之前的限制 (Go < 1.14):
在 Go 1.14 之前,如果你嘗試嵌入兩個具有同名同簽名方法的接口,編譯器會報錯。例如:
package main
import "fmt"
type Reader interface {
Read(p []byte) (n int, err error)
Close() error
}
type Writer interface {
Write(p []byte) (n int, err error)
Close() error // 與 Reader 中的 Close 方法簽名相同
}
// 在 Go 1.14 之前,下面的定義會導(dǎo)致編譯錯誤:
// "ambiguous selector io.ReadWriteCloser.Close" 或
// "duplicate method Close"
/*
type ReadWriter interface {
Reader // 嵌入 Reader
Writer // 嵌入 Writer (包含重復(fù)的 Close 方法)
}
*/
func main() {
fmt.Println("Go 1.14 之前的接口嵌入限制示例")
// 無法直接定義包含重復(fù)方法的嵌入接口
}
Go 1.14 的改進(jìn):
Go 1.14 允許這種情況。當(dāng)一個接口嵌入多個包含相同方法(名稱和簽名一致)的接口時,這些相同的方法在最終的接口方法集中只會出現(xiàn)一次。
package main
import (
"fmt"
"io" // 使用標(biāo)準(zhǔn)庫接口作為例子
)
// io.ReadCloser 定義
// type ReadCloser interface {
// Reader
// Closer
// }
// io.WriteCloser 定義
// type WriteCloser interface {
// Writer
// Closer // 與 ReadCloser 中的 Closer 方法簽名相同
// }
// Go 1.14 及之后版本,下面的定義是合法的
type ReadWriteCloser interface {
io.ReadCloser // 嵌入 io.ReadCloser (包含 Close)
io.WriteCloser // 嵌入 io.WriteCloser (包含 Close)
// 最終的 ReadWriteCloser 接口包含 Read, Write, 和 一個 Close 方法
}
type myReadWriteCloser struct{}
func (m *myReadWriteCloser) Read(p []byte) (n int, err error) {
fmt.Println("Reading...")
return 0, nil
}
func (m *myReadWriteCloser) Write(p []byte) (n int, err error) {
fmt.Println("Writing...")
return len(p), nil
}
func (m *myReadWriteCloser) Close() error {
fmt.Println("Closing...")
return nil
}
func main() {
var rwc ReadWriteCloser
rwc = &myReadWriteCloser{}
rwc.Read(nil)
rwc.Write([]byte("test"))
rwc.Close() // 調(diào)用的是同一個 Close 方法
// 檢查是否同時滿足 io.ReadCloser 和 io.WriteCloser
var rc io.ReadCloser = rwc
var wc io.WriteCloser = rwc
fmt.Printf("rwc is ReadCloser: %t\n", rc != nil)
fmt.Printf("rwc is WriteCloser: %t\n", wc != nil)
}
在這個例子中,ReadWriteCloser 嵌入了 io.ReadCloser 和 io.WriteCloser。兩者都包含了一個 Close() error 方法。在 Go 1.14 中,這是允許的,ReadWriteCloser 接口最終只包含一個 Close 方法。任何實現(xiàn)了 ReadWriteCloser 的類型,其 Close 方法必須同時滿足 io.ReadCloser 和 io.WriteCloser 的要求。
重要提示: 這個改動只適用于 嵌入 的接口。如果在一個接口定義中 顯式聲明 了同名同簽名的方法,或者顯式聲明的方法與嵌入接口中的方法沖突,依然會和以前一樣導(dǎo)致編譯錯誤。
package main
import "io"
// 這個定義仍然是錯誤的,因為 Close 被顯式聲明了兩次
/*
type BadInterface interface {
Close() error
Close() error // compile error: duplicate method Close
}
*/
// 這個定義也是錯誤的,因為顯式聲明的 Close 與嵌入的 Close 沖突
/*
type AnotherBadInterface interface {
io.Closer // 嵌入 io.Closer (包含 Close() error)
Close() error // compile error: duplicate method Close
}
*/
func main() {}
這個改進(jìn)使得接口設(shè)計,尤其是在構(gòu)建復(fù)雜的接口層次結(jié)構(gòu)時更加靈活。
模塊與 Vendoring 行為變更
Go 1.14 對 Go Modules 的 vendor 機制和模塊下載進(jìn)行了一些重要的調(diào)整和改進(jìn)。
默認(rèn)啟用 -mod=vendor:
最顯著的變化是 go 命令(如 go build, go test, go run 等接受 -mod 標(biāo)志的命令)在特定條件下的默認(rèn)行為。
- 條件:
- 你的主模塊(項目根目錄)包含一個名為 vendor 的頂層目錄。
- 你的主模塊的 go.mod 文件中指定了 go 1.14 或更高的 Go 版本 (go 1.14, go 1.15, 等等)。
- 行為:如果滿足以上兩個條件,go 命令現(xiàn)在會 默認(rèn) 使用 -mod=vendor 標(biāo)志。這意味著構(gòu)建、測試等操作會優(yōu)先使用 vendor 目錄中的依賴包,而不是去模塊緩存($GOPATH/pkg/mod)中查找。
對比 (Go < 1.14 或 無 vendor 目錄):
在 Go 1.14 之前,或者即使在 Go 1.14+ 但沒有 vendor 目錄,或者 go.mod 指定的版本低于 1.14,go 命令默認(rèn)的行為類似于 -mod=readonly,它會使用模塊緩存中的依賴。
新的 -mod=mod 標(biāo)志:
為了應(yīng)對默認(rèn)行為的改變,Go 1.14 引入了一個新的 -mod 標(biāo)志值:-mod=mod。如果你滿足了默認(rèn)啟用 vendor 模式的條件,但又想強制 go 命令使用模塊緩存(就像沒有 vendor 目錄時那樣),你可以顯式地使用 -mod=mod 標(biāo)志。
# 假設(shè)項目滿足條件 (go.mod >= 1.14, vendor/ 存在)
# Go 1.14+ 默認(rèn)行為,等同于 go build -mod=vendor
go build
# 強制使用 module cache,忽略 vendor/ 目錄
go build -mod=mod
vendor/modules.txt 校驗:
當(dāng) -mod=vendor 被設(shè)置時(無論是顯式設(shè)置還是默認(rèn)啟用),go 命令現(xiàn)在會校驗主模塊下的 vendor/modules.txt 文件是否與其 go.mod 文件保持一致。如果不一致,命令會報錯。這有助于確保 vendor 目錄的內(nèi)容確實反映了 go.mod 文件中聲明的依賴。
go list -m 行為變更:
在 vendor 模式下 (-mod=vendor),go list -m 命令不再會靜默地忽略那些在 vendor 目錄中找不到對應(yīng)包的 傳遞性依賴(transitive dependencies)。如果請求信息的模塊沒有在 vendor/modules.txt 文件中列出,go list -m 現(xiàn)在會明確地報錯失敗。
模塊下載改進(jìn):
- Subversion 支持 :go 命令在模塊模式下現(xiàn)在支持從 Subversion (SVN) 版本控制系統(tǒng)下載模塊。
- 更清晰的錯誤信息 :當(dāng)從模塊代理(Module Proxies)或其他 HTTP 服務(wù)器下載模塊遇到錯誤時,go 命令現(xiàn)在會嘗試包含一部分來自服務(wù)器的純文本錯誤信息片段。這有助于診斷下載問題。只有當(dāng)錯誤信息是有效的 UTF-8 編碼,并且只包含圖形字符和空格時,才會被顯示。
這些改動使得 vendor 模式更加健壯和符合預(yù)期,同時也提升了模塊下載的兼容性和問題診斷能力。
運行時性能改進(jìn)和 Goroutine 搶占
Go 1.14 在運行時(runtime)層面引入了多項重要的性能改進(jìn)和機制變化。
defer 性能大幅提升:
Go 1.14 顯著優(yōu)化了 defer 語句的實現(xiàn)。對于大多數(shù)使用場景,defer 的開銷已經(jīng)降低到幾乎為零,與直接調(diào)用被延遲的函數(shù)相差無幾。
- 影響:這意味著開發(fā)者可以在性能敏感的代碼路徑中(例如,循環(huán)內(nèi)部)更自由地使用 defer 來進(jìn)行資源清理(如 Unlock 互斥鎖、關(guān)閉文件句柄等),而不必過分擔(dān)心其帶來的性能損耗。
- 對比 (Go < 1.14) :在舊版本中,defer 會帶來一定的固定開銷,可能導(dǎo)致開發(fā)者在性能關(guān)鍵區(qū)域避免使用它,轉(zhuǎn)而采用手動調(diào)用清理函數(shù)的方式。
雖然很難用簡單的代碼示例直接 展示 性能差異(需要基準(zhǔn)測試),但可以想象在舊版本中可能避免的寫法:
// 在 Go 1.14+ 中,即使在循環(huán)內(nèi)部,使用 defer 的性能開銷也大大降低
func processItems(items []Item, mu *sync.Mutex) {
for _, item := range items {
mu.Lock()
// 在 Go 1.14+,這里的 defer 開銷很小
defer mu.Unlock()
// ... 處理 item ...
if item.needsSpecialHandling() {
// 在 Go 1.14 之前,可能會因為性能考慮,在這里手動 Unlock
// mu.Unlock()
handleSpecial(item)
// continue // 或者 return,需要確保 Unlock 被調(diào)用
// 并且在循環(huán)正常結(jié)束時也需要 Unlock,代碼更復(fù)雜
// mu.Lock() // 如果 continue 后還需要鎖
}
}
}
Goroutine 異步搶占式調(diào)度:
這是一個重要的底層調(diào)度機制變化。Goroutine 現(xiàn)在是 異步搶占(asynchronously preemptible) 的。
- 機制:在此之前,Go 的調(diào)度器是協(xié)作式的,搶占點主要發(fā)生在函數(shù)調(diào)用時。如果一個 Goroutine 執(zhí)行一個沒有函數(shù)調(diào)用的密集計算循環(huán)(例如 for {}),它可能會長時間霸占 CPU,導(dǎo)致其他 Goroutine 無法運行,甚至可能阻塞調(diào)度器或顯著延遲垃圾回收(GC)。
- 改進(jìn):Go 1.14 引入了基于信號的異步搶占機制。這意味著即使 Goroutine 正在執(zhí)行一個沒有函數(shù)調(diào)用的循環(huán),運行時也可以發(fā)送信號來中斷它,讓調(diào)度器有機會運行其他 Goroutine 或執(zhí)行 GC。
- 影響:
a.提高了程序的公平性和響應(yīng)性,避免了某些類型的死鎖或調(diào)度延遲。
b.密集計算循環(huán)不再容易餓死其他 Goroutine 或 GC。
- 平臺支持:此功能在發(fā)布時支持除 windows/arm, darwin/arm, js/wasm, plan9/* 之外的所有平臺。
- 副作用 (EINTR 錯誤) :這種基于信號的搶占實現(xiàn)有一個副作用:在 Unix 系統(tǒng)(包括 Linux 和 macOS)上,用 Go 1.14 構(gòu)建的程序可能會比舊版本接收到更多的信號。這會導(dǎo)致那些進(jìn)行 慢系統(tǒng)調(diào)用(slow system calls) 的代碼(例如,使用 syscall 或 golang.org/x/sys/unix 包進(jìn)行網(wǎng)絡(luò)讀寫、文件操作等)更頻繁地遇到 EINTR (Interrupted system call) 錯誤。
- 應(yīng)對:程序 必須 正確處理 EINTR 錯誤,通常的做法是簡單地重試該系統(tǒng)調(diào)用。
import "syscall"
import "fmt"
// 示例:處理可能因搶占信號而中斷的系統(tǒng)調(diào)用
func readFileWithRetry(fd int, buf []byte) (int, error) {
for {
n, err := syscall.Read(fd, buf) // Read 是一個可能被信號中斷的系統(tǒng)調(diào)用
// 如果錯誤是 EINTR,說明系統(tǒng)調(diào)用被信號中斷了(可能是搶占信號)
// 我們應(yīng)該重試這個操作
if err == syscall.EINTR {
fmt.Println("Syscall interrupted (EINTR), retrying...")
continue
}
// 如果是其他錯誤,或者沒有錯誤 (n >= 0)
// 則返回結(jié)果
return n, err
}
}
內(nèi)存分配器 (Page Allocator) 效率提升:
Go 1.14 的頁面分配器(Page Allocator)效率更高,并且在高 GOMAXPROCS 值(即使用大量 CPU 核心時)顯著減少了鎖競爭。
- 影響 :這主要體現(xiàn)在并行執(zhí)行大量大內(nèi)存分配(large allocations)時,可以觀察到更低的延遲和更高的吞吐量。
內(nèi)部計時器 (Internal Timers) 效率提升:
運行時內(nèi)部使用的計時器(被 time.After, time.Tick, net.Conn.SetDeadline 等標(biāo)準(zhǔn)庫函數(shù)依賴)也得到了優(yōu)化。
- 影響 :減少了鎖競爭和上下文切換次數(shù)。這是一個內(nèi)部性能改進(jìn),理論上不會導(dǎo)致用戶可見的行為變化,但會提升依賴這些計時器的操作的整體性能。
總的來說,Go 1.14 在運行時層面帶來了顯著的性能提升和調(diào)度魯棒性增強,但也引入了需要開發(fā)者注意的 EINTR 錯誤處理要求。
Go Modules: 無 go.mod 文件及不兼容版本處理
Go 1.14 對 Go Modules 在特定場景下的行為進(jìn)行了調(diào)整,旨在提高構(gòu)建的確定性和可復(fù)現(xiàn)性。
模塊感知模式下無 go.mod 文件的行為:
當(dāng)顯式啟用模塊感知模式(通過設(shè)置環(huán)境變量 GO111MODULE=on),但當(dāng)前目錄及所有父目錄中都 沒有 找到 go.mod 文件時,大多數(shù)與模塊相關(guān)的 go 命令(如 go build, go run, go test 等)的功能會受到限制。
- 限制 :在沒有 go.mod 的情況下,這些命令只能構(gòu)建:
a.標(biāo)準(zhǔn)庫中的包 (e.g., fmt, net/http)。
b.在命令行上直接指定的 .go 文件。
- 原因 :在 Go 1.14 之前,即使沒有 go.mod,go 命令也會嘗試解析包路徑,并隱式地去下載和使用它能找到的最新版本的模塊。然而,這種方式 不會記錄 下來具體使用了哪個模塊的哪個版本。這導(dǎo)致了兩個問題:
a.構(gòu)建速度慢 :每次構(gòu)建可能都需要重新解析和下載。
b.不可復(fù)現(xiàn) :不同時間或不同環(huán)境下執(zhí)行相同的命令,可能會因為依賴的最新版本發(fā)生變化而得到不同的結(jié)果,甚至構(gòu)建失敗。
c.Go 1.14 的改變 :為了強制實現(xiàn)可復(fù)現(xiàn)構(gòu)建,Go 1.14 限制了在無 go.mod 時隱式解析和下載依賴的能力。你需要一個 go.mod 文件來明確管理你的項目依賴。
不受影響的命令:
需要注意的是,以下命令的行為基本保持不變,即使在沒有 go.mod 的模塊感知模式下:
- go get <path>@<version>:仍然可以用于下載指定版本的模塊到模塊緩存。
- go mod download <path>@<version>:同上。
- go list -m <path>@<version>:仍然可以查詢指定版本模塊的信息。
# 確保模塊模式開啟
export GO111MODULE=on
# 創(chuàng)建一個沒有 go.mod 的目錄
mkdir /tmp/no_gomod_test
cd /tmp/no_gomod_test
# 創(chuàng)建一個簡單的 main.go
echo 'package main; import "fmt"; func main() { fmt.Println("Hello from main.go") }' > main.go
# 1. 構(gòu)建標(biāo)準(zhǔn)庫包 (可以)
# (這個命令本身意義不大,只是演示可以訪問標(biāo)準(zhǔn)庫)
# go build fmt
# 2. 構(gòu)建命令行指定的 .go 文件 (可以)
go build main.go
./main # 輸出: Hello from main.go
# 3. 嘗試構(gòu)建一個需要外部依賴的 .go 文件 (如果依賴未下載則會失敗)
# echo 'package main; import "rsc.io/quote"; func main() { println(quote.Go()) }' > need_dep.go
# go build need_dep.go # Go 1.14+ 會報錯,無法找到 rsc.io/quote
# 4. 嘗試直接運行需要外部依賴的包 (Go 1.14+ 會報錯)
# go run rsc.io/quote/cmd/quote # Go 1.14+ 報錯
# 5. 使用 go get 下載特定版本 (仍然可以)
go get rsc.io/quote@v1.5.2
# 現(xiàn)在再運行上面的 go build need_dep.go 或 go run ... 可能會成功,因為它在緩存里了
# 但這仍然不是推薦的工作方式,因為它沒有被 go.mod 記錄
cd ..
rm -rf /tmp/no_gomod_test
處理不兼容的主版本 (+incompatible):
Go Modules 使用語義化版本(Semantic Versioning)。主版本號(Major Version)的改變通常意味著不兼容的 API 變更。Go 1.14 對 go get 和 go list 處理不兼容主版本的方式進(jìn)行了調(diào)整。
- 條件 :當(dāng)你嘗試獲取或更新一個模塊,并且該模塊的 最新版本 已經(jīng)包含了 go.mod 文件時。
- go get 的行為 :
a.默認(rèn)情況下,go get 將 不再 自動將你的依賴升級到一個 不兼容的主版本 (例如,從 v1.x.y 升級到 v2.0.0 或更高版本)。
b.它只會升級到當(dāng)前主版本內(nèi)的最新兼容版本(例如,從 v1.4.0 升級到 v1.5.2)。
如果你確實 需要 升級到不兼容的主版本,你必須 顯式 地指定該版本(例如 go get example.com/mod@v2.0.0),或者該不兼容版本已經(jīng)是你項目依賴圖中某個其他模塊所必需的依賴。
- go list 的行為 :
a.當(dāng) go list 直接從版本控制系統(tǒng)(如 Git)獲取模塊信息時,它通常也會忽略那些被視為不兼容的主版本(相對于當(dāng)前已知的版本)。
b.但是,如果信息是從模塊代理獲取的,代理可能會報告所有可用的版本,包括不兼容的主版本,這時 go list 可能會包含它們。
這個改變有助于防止意外引入破壞性的 API 變更,使得依賴管理更加安全和可控。對于那些在引入 Go Modules 之前就已經(jīng)發(fā)布了 v2+ 版本但沒有遵循模塊路徑約定的模塊,Go 會使用 +incompatible 標(biāo)記(例如 example.com/mod v2.0.1+incompatible)來標(biāo)識它們。
# 假設(shè) example.com/mod 有以下版本:
# v1.5.0 (有 go.mod)
# v2.1.0 (有 go.mod)
# 當(dāng)前項目的 go.mod 文件:
# module myproject
# go 1.14
# require example.com/mod v1.4.0
# 運行 go get 更新依賴
go get example.com/mod
# 在 Go 1.14+, 這通常會將 go.mod 更新到 require example.com/mod v1.5.0
# 而不會跳到 v2.1.0
# 如果確實想使用 v2.1.0,必須顯式指定
go get example.com/mod@v2.1.0
# 這會將 go.mod 更新到 require example.com/mod/v2 v2.1.0 (如果 v2 遵循了模塊路徑約定)
# 或者 require example.com/mod v2.1.0+incompatible (如果 v2 沒有遵循約定)
新增 hash/maphash 包
Go 1.14 標(biāo)準(zhǔn)庫中增加了一個新的包:hash/maphash。這個包提供了一種用于對字節(jié)序列([]byte 或 string)進(jìn)行哈希計算的函數(shù)。
主要用途:
hash/maphash 主要設(shè)計用于實現(xiàn) 哈希表(hash tables, 在 Go 中通常指 map)或其他需要將任意字符串或字節(jié)序列映射到 64 位無符號整數(shù)(uint64)上,并期望結(jié)果具有良好均勻分布的數(shù)據(jù)結(jié)構(gòu)。
核心特性:
- 高性能: 該哈希算法經(jīng)過優(yōu)化,執(zhí)行速度非常快。
- 抗碰撞性 (Collision-Resistant): 算法設(shè)計旨在最小化不同輸入產(chǎn)生相同哈希值的概率(哈希碰撞),使得哈希值分布均勻。這對于哈希表的性能至關(guān)重要。
- 非加密安全 (Not Cryptographically Secure): 極其重要 的一點是,hash/maphash不是 加密安全的哈希函數(shù)。你不應(yīng)該將它用于任何安全相關(guān)的目的,例如:
a.密碼哈希存儲
b.生成消息認(rèn)證碼 (MAC)
c.數(shù)字簽名
任何需要抵抗惡意攻擊者尋找碰撞或原像的場景 對于這些場景,應(yīng)該使用 crypto/sha256, crypto/sha512, golang.org/x/crypto/bcrypt 等加密哈希庫。
- 進(jìn)程內(nèi)穩(wěn)定,跨進(jìn)程不穩(wěn)定:
- 對于一個給定的字節(jié)序列,在 同一個 Go 進(jìn)程 的單次執(zhí)行過程中,其 maphash 哈希值是 穩(wěn)定不變 的。
- 但是,對于同一個字節(jié)序列,在 不同的 Go 進(jìn)程 中,或者 同 一個程序的多次不同執(zhí)行 中,計算出的 maphash 哈希值 幾乎肯定會不同。
為什么跨進(jìn)程不穩(wěn)定?
這是故意設(shè)計的。maphash 使用一個 哈希種子(seed) 來初始化其內(nèi)部狀態(tài)。這個種子在每個 Go 程序啟動時由運行時隨機生成(通過 maphash.MakeSeed())。這意味著每次運行程序時,哈希函數(shù)都會使用不同的種子,從而產(chǎn)生不同的哈希序列。
這種設(shè)計的主要目的是 **防止 哈希洪水攻擊 (Hash Flooding Attacks)**。這類攻擊依賴于攻擊者能夠預(yù)測哈希函數(shù)對于特定輸入的輸出,從而構(gòu)造大量會導(dǎo)致哈希碰撞的輸入,使得哈希表性能急劇下降(從 O(1) 退化到 O(n)),導(dǎo)致拒絕服務(wù)(Denial of Service, DoS)。由于種子在每次運行時都不同,攻擊者無法預(yù)先構(gòu)造出在特定運行實例中必然會碰撞的輸入。
基本用法:
package main
import (
"fmt"
"hash/maphash"
)
func main() {
// 1. 創(chuàng)建一個 maphash.Hash 實例
// 它會自動使用當(dāng)前進(jìn)程的隨機種子進(jìn)行初始化
var h maphash.Hash
// 如果需要對同一個哈希對象計算多個哈希值,需要 Reset
// (或者為每個值創(chuàng)建新的 Hash 對象)
// 2. 添加數(shù)據(jù) (string 或 []byte)
s1 := "hello maphash"
h.WriteString(s1)
// 3. 計算 64 位哈希值
hash1 := h.Sum64()
fmt.Printf("Hash of \"%s\": %d (0x%x)\n", s1, hash1, hash1)
// 4. Reset 并計算另一個值
h.Reset()
s2 := []byte("hello maphash") // 相同內(nèi)容,不同類型
h.Write(s2)
hash2 := h.Sum64()
// 注意:即使內(nèi)容相同,直接比較 []byte 和 string 的哈希值通常也需要確保它們字節(jié)表示一致
fmt.Printf("Hash of []byte(\"%s\"): %d (0x%x)\n", string(s2), hash2, hash2)
// 在這個例子中,string 和 []byte 的內(nèi)容完全相同,所以哈希值也應(yīng)該相同
fmt.Printf("Hash values match: %t\n", hash1 == hash2)
// 5. 計算第三個值
h.Reset()
s3 := "another value"
h.WriteString(s3)
hash3 := h.Sum64()
fmt.Printf("Hash of \"%s\": %d (0x%x)\n", s3, hash3, hash3)
// 6. 再次計算第一個值,驗證進(jìn)程內(nèi)穩(wěn)定性
h.Reset()
h.WriteString(s1)
hash4 := h.Sum64()
fmt.Printf("Hash of \"%s\" again: %d (0x%x)\n", s1, hash4, hash4)
fmt.Printf("Process-local stability check (hash1 == hash4): %t\n", hash1 == hash4)
fmt.Println("\nRun this program again, the hash values will likely be different.")
// 你也可以顯式管理種子,但這通常只在特殊情況下需要
// seed := maphash.MakeSeed()
// h.SetSeed(seed)
// ...
}
輸出:
Hash of "hello maphash": 16786359967769308781 (0xe8f52173e6ba2e6d)
Hash of []byte("hello maphash"): 16786359967769308781 (0xe8f52173e6ba2e6d)
Hash values match: true
Hash of "another value": 14091924103374798602 (0xc390924f4f6b7f0a)
Hash of "hello maphash" again: 16786359967769308781 (0xe8f52173e6ba2e6d)
Process-local stability check (hash1 == hash4): true
Run this program again, the hash values will likely be different.
如果你運行上面的程序多次,你會發(fā)現(xiàn)每次運行時輸出的哈希值都不同,但每次運行內(nèi)部 hash1 和 hash4 的值總是相同的。
hash/maphash 為 Go 開發(fā)者提供了一個內(nèi)置的、快速且適合用于哈希表實現(xiàn)的哈希函數(shù),同時通過隨機種子避免了潛在的安全風(fēng)險。
參考資料
[1] overlapping interfaces proposal: https://go.googlesource.com/proposal/+/master/design/6977-overlapping-interfaces.md