Go1.18 快訊:新增字符串 Clone API
閱讀本文大概需要 5 分鐘。
大家好,我是 polarisxu。
Go 1.18 雖然還有 4 個(gè)月發(fā)布,但大部分的功能基本確定。我們可以提前知曉、熟悉。
今天介紹的是標(biāo)準(zhǔn)庫(kù)中新增的一個(gè) API:strings.Clone()。
從名稱可以知道,這是克隆。很多其他語(yǔ)言一開始就有這樣的功能。比如 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 復(fù)制到一個(gè)新分配的副本中,當(dāng)只保留一個(gè)很大的字符串中的一個(gè)小子字符串時(shí),這一點(diǎn)很重要。使用克隆可以幫助這些程序使用更少的內(nèi)存。當(dāng)然,由于使用克隆制作拷貝,過(guò)度使用克隆會(huì)使程序使用更多內(nèi)存。通常,只有在分析表明需要克隆時(shí),才謹(jǐn)慎使用克隆。對(duì)于長(zhǎng)度為零的字符串,將返回字符串 "",不進(jìn)行內(nèi)存分配。
02 舉例說(shuō)明
大家可能還是迷惑,不知道有啥用。舉一個(gè)代碼例子說(shuō)明:
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)是對(duì)字符串底層結(jié)構(gòu)的反射表示。
在上面示例場(chǎng)景中,如果 s 很大,而之后我們只需要使用它的某個(gè)短子串,這會(huì)導(dǎo)致內(nèi)存的浪費(fèi),因?yàn)樽哟驮址?Data 部分指向相同的內(nèi)存,因此整個(gè)字符串并不會(huì)被 GC 回收。
strings.Clone 函數(shù)就是為了解決這個(gè)問(wèn)題的:(要正常運(yùn)行下面代碼,需要按照 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
通過(guò)克隆得到 s2,從最后輸出結(jié)果看,Data 已經(jīng)不同了,原始的長(zhǎng)字符串就可以被垃圾回收了。(你也可以將傳遞給 Clone 的參數(shù)改為 s1,后面部分用 s1 和 s2 比)
03 內(nèi)部實(shí)現(xiàn)
知道了克隆的用途,再看看 strings.Clone 的實(shí)現(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))
}
這里有兩個(gè)關(guān)鍵點(diǎn):
通過(guò) copy 進(jìn)行拷貝。其實(shí)普通的 slice,也會(huì)有需要克隆的場(chǎng)景,這時(shí),需要我們手動(dòng)執(zhí)行 copy 操作。 return 后面的語(yǔ)句 *(*string)(unsafe.Pointer(&b)),實(shí)現(xiàn) []byte 到 string 的零內(nèi)存拷貝轉(zhuǎn)換。
04 總結(jié)
Go 雖然有 GC,大部分時(shí)候不需要考慮內(nèi)存問(wèn)題,但對(duì)內(nèi)存的使用,我們需要有敬畏之心,特別是大塊內(nèi)存、重復(fù)分配內(nèi)存的場(chǎng)景,我們需要知曉如何優(yōu)化,寫出真正高質(zhì)量的代碼。
strings.Clone 的使用很簡(jiǎn)單,但希望通過(guò)本文,你在寫 Go 代碼時(shí),對(duì)類似場(chǎng)景下,slice 的正確使用有啟發(fā)(string 可以認(rèn)為是特殊的 slice)。
我是 polarisxu,北大碩士畢業(yè),曾在 360 等知名互聯(lián)網(wǎng)公司工作,10多年技術(shù)研發(fā)與架構(gòu)經(jīng)驗(yàn)!2012 年接觸 Go 語(yǔ)言并創(chuàng)建了 Go 語(yǔ)言中文網(wǎng)!著有《Go語(yǔ)言編程之旅》、開源圖書《Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)》等。
堅(jiān)持輸出技術(shù)(包括 Go、Rust 等技術(shù))、職場(chǎng)心得和創(chuàng)業(yè)感悟!歡迎關(guān)注「polarisxu」一起成長(zhǎng)!也歡迎加我微信好友交流:gopherstudio
