大家好,我是煎魚。
Go1.18 過幾周(3月份)就要釋出了,先前我們已經更新了好幾期新版本特性,今天給大家帶來一個新的優化類的內容,是與 strings 和 bytes 標準庫有關。
背景
想要更快捷複製
在日常程式設計中,位元組 []byte
是經常需要複製的。需要寫以下程式碼:
dup := make([]byte, len(data))
copy(dup, data)
@Ilia Choly 覺得這就太麻煩了,畢竟每次都得寫一遍,又或是自己封裝成如下函式:
// Clone returns a copy of b
func Clone(b []byte) []byte {
b2 := make([]byte, len(b))
copy(b2, b)
return b2
}
為此想要增加一個快捷方法,不過這顯然站不住腳,熟悉語法的同學會發現有現成的方式:
b2 := append([]byte(nil), b...)
一行就能實現效果,甚至更快,因為分配的切片不會被初始化為零值。
複製會共享記憶體
許多 Go 開發者,在寫應用程式要複製切片(Slice)時,會發現複製出來的切片 s1 和原始的 s0 有記憶體上的關聯,其本質原因在於其底層的資料結構導致,這會導致許多隱藏的問題。
示例程式碼如下:
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
s0 := "腦子進煎魚了"
s1 := s0[:3]
s0h := (*reflect.StringHeader)(unsafe.Pointer(&s0))
s1h := (*reflect.StringHeader)(unsafe.Pointer(&s1))
fmt.Printf("Len is equal: %t\n", s0h.Len == s1h.Len)
fmt.Printf("Data is equa: %t\n", s0h.Data == s1h.Data)
}
從上述程式來看,你認為變數 s0 和 s1 相比,他們的 Len 相等嗎?Data 相等嗎?
輸出結果如下:
Len is equal: false
Data is equa: true
咋一看,好像沒問題。Len 不相等,畢竟是按索引複製的。但 Data 竟然就相等了,這為什麼,是 Go 出 BUG 了嗎?
這實際是與 Go 在 String 和 Slice 的底層資料結構有關的,例如 String 的執行時表現是 StringHeader。
他的底層結構如下:
type StringHeader struct {
Data uintptr
Len int
}
- Data: 指向具體的底層陣列。
- Len:代表字串切片的長度。
關鍵就在於 Data,本質上是一個指向底層資料的指標地址,因此在複製時,會將其也複製過去。造成不必要的複製和 “髒” 資料,引發各種奇怪的 BUG。
這個問題,正在新特性的痛點。
新特性
在 Go1.18 的新特性中,strings 和 bytes 增加了一個 Clone 方法,來解決前面提到的 2 個問題點。
原始碼如下:
func Clone(s string) string {
if len(s) == 0 {
return ""
}
b := make([]byte, len(s))
copy(b, s)
return *(*string)(unsafe.Pointer(&b))
}
- 通過
copy
函式對原始字串進行復制,得到一份新的[]byte
資料。 - 通過
*(*string)(unsafe.Pointer(&b))
進行指標操作,實現byte
到string
的零記憶體複製轉換。
至此,通過非常巧妙地方式解決了背景中的兩個問題。大家也不用一直去重複寫類似的程式碼了。
總結
一直以來,Go 中的 string 和 slice 因為底層陣列指向和擴縮容機制等原因飽受爭議,在末尾也給出一些公眾號之前介紹過的一些 “坑”,有興趣的讀者可以看看。
本次的 1.18 新方法更新,是有一定功能作用的,在學習上也能夠明確的知道 Clone 他的作用和定義,會留個心眼。
你的專案中有類似的程式碼嗎?
若有任何疑問歡迎評論區反饋和交流,最好的關係是互相成就,各位的點贊就是煎魚創作的最大動力,感謝支援。
文章持續更新,可以微信搜【腦子進煎魚了】閱讀,本文 GitHub github.com/eddycjy/blog 已收錄,學習 Go 語言可以看 Go 學習地圖和路線,歡迎 Star 催更。
往期推薦
參考
- bytes, strings: add Clone
- [test: add test that we don't optimize string([]byte(string))](https://github.com/golang/go/...)
- proposal: provide std lib pkg Clone function to copy a string efficiently without memory sharing