Go1.18 快訊:字符串 Clone 有什么用?
閱讀本文大概需要 6?分鐘。
大家好,我是 polarisxu。
Go 1.18 雖然還有 4 個月發(fā)布,但大部分的功能基本確定。我們可以提前知曉、熟悉。
今天介紹的是標準庫中新增的一個 API:strings.Clone()。
從名稱可以知道,這是克隆。很多其他語言一開始就有這樣的功能。比如 PHP 有 clone 關(guān)鍵字、__clone 魔術(shù)方法;Java 的根類 Object 有 clone 方法等。
01 函數(shù)簽名
該函數(shù)的定義如下(見:https://pkg.go.dev/strings@master#Clone)
//?Clone?returns?a?fresh?copy?of?s.
//?It?guarantees?to?make?a?copy?of?s?into?a?new?allocation,
//?which?can?be?important?when?retaining?only?a?small?substring
//?of?a?much?larger?string.?Using?Clone?can?help?such?programs
//?use?less?memory.?Of?course,?since?using?Clone?makes?a?copy,
//?overuse?of?Clone?can?make?programs?use?more?memory.
//?Clone?should?typically?be?used?only?rarely,?and?only?when
//?profiling?indicates?that?it?is?needed.
//?For?strings?of?length?zero?the?string?""?will?be?returned
//?and?no?allocation?is?made.
func?Clone(s?string)?string
Clone 返回 s 的新副本。它保證將 s 復制到一個新分配的副本中,當只保留一個很大的字符串中的一個小子字符串時,這一點很重要。使用克隆可以幫助這些程序使用更少的內(nèi)存。當然,由于使用克隆制作拷貝,過度使用克隆會使程序使用更多內(nèi)存。通常,只有在分析表明需要克隆時,才謹慎使用克隆。對于長度為零的字符串,將返回字符串 "",不進行內(nèi)存分配。
02 舉例說明
大家可能還是迷惑,不知道有啥用。舉一個代碼例子說明:
package?main
import?(
?"fmt"
?"reflect"
?"unsafe"
)
func?main()?{
?s?:=?"abcdefghijklmn"
?s1?:=?s[:4]
?sHeader?:=?(*reflect.StringHeader)(unsafe.Pointer(&s))
?s1Header?:=?(*reflect.StringHeader)(unsafe.Pointer(&s1))
?fmt.Println(sHeader.Len?==?s1Header.Len)
?fmt.Println(sHeader.Data?==?s1Header.Data)
??
??//?Output:
??//?false
??//?true
}
Len 不相等不需要解釋,Data 相等就值得注意。
上面代碼,有些人可能不知道什么意思。這里涉及到 Go 中 string 類型的底層結(jié)構(gòu)。在 Go 中,string 類型的底層表示如下:
type?string?struct?{
?ptr?unsafe.Pointer
?len?int
}而 reflect.StringHeader 結(jié)構(gòu)是對字符串底層結(jié)構(gòu)的反射表示。
在上面示例場景中,如果 s 很大,而之后我們只需要使用它的某個短子串,這會導致內(nèi)存的浪費,因為子串和原字符串的 Data 部分指向相同的內(nèi)存,因此整個字符串并不會被 GC 回收。
strings.Clone 函數(shù)就是為了解決這個問題的:(要正常運行下面代碼,需要按照 Go tip 版本)
s2?:=?strings.Clone(s[:4])
s2Header?:=?(*reflect.StringHeader)(unsafe.Pointer(&s2))
fmt.Println(sHeader.Len?==?s2Header.Len)
fmt.Println(sHeader.Data?==?s2Header.Data)
//?Output:
//?false
//?false
通過克隆得到 s2,從最后輸出結(jié)果看,Data 已經(jīng)不同了,原始的長字符串就可以被垃圾回收了。(你也可以將傳遞給 Clone 的參數(shù)改為 s1,后面部分用 s1 和 s2 比)
03 內(nèi)部實現(xiàn)
知道了克隆的用途,再看看 strings.Clone 的實現(xiàn)。
func?Clone(s?string)?string?{
?if?len(s)?==?0?{
??return?""
?}
?b?:=?make([]byte,?len(s))
?copy(b,?s)
?return?*(*string)(unsafe.Pointer(&b))
}
這里有兩個關(guān)鍵點:
通過 copy 進行拷貝。其實普通的 slice,也會有需要克隆的場景,這時,需要我們手動執(zhí)行 copy 操作。 return 后面的語句 *(*string)(unsafe.Pointer(&b)),實現(xiàn) []byte 到 string 的零內(nèi)存拷貝轉(zhuǎn)換。
04 總結(jié)
Go 雖然有 GC,大部分時候不需要考慮內(nèi)存問題,但對內(nèi)存的使用,我們需要有敬畏之心,特別是大塊內(nèi)存、重復分配內(nèi)存的場景,我們需要知曉如何優(yōu)化,寫出真正高質(zhì)量的代碼。
strings.Clone 的使用很簡單,但希望通過本文,你在寫 Go 代碼時,對類似場景下,slice 的正確使用有啟發(fā)(string 可以認為是特殊的 slice)。
推薦閱讀
