Go1.18 新特性:高效複製,strings, bytes 標準庫新增 Clone API

煎魚發表於2022-02-24

大家好,我是煎魚。

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)) 進行指標操作,實現 bytestring 的零記憶體複製轉換。

至此,通過非常巧妙地方式解決了背景中的兩個問題。大家也不用一直去重複寫類似的程式碼了。

總結

一直以來,Go 中的 string 和 slice 因為底層陣列指向和擴縮容機制等原因飽受爭議,在末尾也給出一些公眾號之前介紹過的一些 “坑”,有興趣的讀者可以看看。

本次的 1.18 新方法更新,是有一定功能作用的,在學習上也能夠明確的知道 Clone 他的作用和定義,會留個心眼。

你的專案中有類似的程式碼嗎?

若有任何疑問歡迎評論區反饋和交流,最好的關係是互相成就,各位的點贊就是煎魚創作的最大動力,感謝支援。

文章持續更新,可以微信搜【腦子進煎魚了】閱讀,本文 GitHub github.com/eddycjy/blog 已收錄,學習 Go 語言可以看 Go 學習地圖和路線,歡迎 Star 催更。

往期推薦

參考

相關文章