這期內(nèi)容當中小編將會給大家?guī)碛嘘P(guān)golang 中slice和string如何使用,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
創(chuàng)新互聯(lián)建站是專業(yè)的黃南州網(wǎng)站建設(shè)公司,黃南州接單;提供網(wǎng)站制作、網(wǎng)站建設(shè),網(wǎng)頁設(shè)計,網(wǎng)站設(shè)計,建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進行黃南州網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團隊,希望更多企業(yè)前來合作!
slice 和 string 的內(nèi)部結(jié)構(gòu)可以在 $GOROOT/src/reflect/value.go
里面找到
type StringHeader struct { Data uintptr Len int } type SliceHeader struct { Data uintptr Len int Cap int }
可以看到一個 string 包含一個數(shù)據(jù)指針和一個長度,長度是不可變的
slice 包含一個數(shù)據(jù)指針、一個長度和一個容量,當容量不夠時會重新申請新的內(nèi)存,Data 指針將指向新的地址,原來的地址空間將被釋放
從這些結(jié)構(gòu)就可以看出,string 和 slice 的賦值,包括當做參數(shù)傳遞,和自定義的結(jié)構(gòu)體一樣,都僅僅是 Data 指針的淺拷貝
si1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} si2 := si1 si2 = append(si2, 0) Convey("重新分配內(nèi)存", func() { header1 := (*reflect.SliceHeader)(unsafe.Pointer(&si1)) header2 := (*reflect.SliceHeader)(unsafe.Pointer(&si2)) fmt.Println(header1.Data) fmt.Println(header2.Data) So(header1.Data, ShouldNotEqual, header2.Data) })
si1 和 si2 開始都指向同一個數(shù)組,當對 si2 執(zhí)行 append 操作時,由于原來的 Cap 值不夠了,需要重新申請新的空間,因此 Data 值發(fā)生了變化,在 $GOROOT/src/reflect/value.go
這個文件里面還有關(guān)于新的 cap 值的策略,在 grow
這個函數(shù)里面,當 cap 小于 1024 的時候,是成倍的增長,超過的時候,每次增長 25%,而這種內(nèi)存增長不僅僅數(shù)據(jù)拷貝(從舊的地址拷貝到新的地址)需要消耗額外的性能,舊地址內(nèi)存的釋放對 gc 也會造成額外的負擔,所以如果能夠知道數(shù)據(jù)的長度的情況下,盡量使用 make([]int, len, cap)
預分配內(nèi)存,不知道長度的情況下,可以考慮下面的內(nèi)存重用的方法
si1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} si2 := si1[:7] Convey("不重新分配內(nèi)存", func() { header1 := (*reflect.SliceHeader)(unsafe.Pointer(&si1)) header2 := (*reflect.SliceHeader)(unsafe.Pointer(&si2)) fmt.Println(header1.Data) fmt.Println(header2.Data) So(header1.Data, ShouldEqual, header2.Data) }) Convey("往切片里面 append 一個值", func() { si2 = append(si2, 10) Convey("改變了原 slice 的值", func() { header1 := (*reflect.SliceHeader)(unsafe.Pointer(&si1)) header2 := (*reflect.SliceHeader)(unsafe.Pointer(&si2)) fmt.Println(header1.Data) fmt.Println(header2.Data) So(header1.Data, ShouldEqual, header2.Data) So(si1[7], ShouldEqual, 10) }) })
si2 是 si1 的一個切片,從第一段代碼可以看到切片并不重新分配內(nèi)存,si2 和 si1 的 Data 指針指向同一片地址,而第二段代碼可以看出,當我們往 si2 里面 append 一個新的值的時候,我們發(fā)現(xiàn)仍然沒有內(nèi)存分配,而且這個操作使得 si1 的值也發(fā)生了改變,因為兩者本就是指向同一片 Data 區(qū)域,利用這個特性,我們只需要讓 si1 = si1[:0]
就可以不斷地清空 si1 的內(nèi)容,實現(xiàn)內(nèi)存的復用了
PS: 你可以使用 copy(si2, si1)
實現(xiàn)深拷貝
Convey("字符串常量", func() { str1 := "hello world" str2 := "hello world" Convey("地址相同", func() { header1 := (*reflect.StringHeader)(unsafe.Pointer(&str1)) header2 := (*reflect.StringHeader)(unsafe.Pointer(&str2)) fmt.Println(header1.Data) fmt.Println(header2.Data) So(header1.Data, ShouldEqual, header2.Data) }) })
這個例子比較簡單,字符串常量使用的是同一片地址區(qū)域
Convey("相同字符串的不同子串", func() { str1 := "hello world"[:6] str2 := "hello world"[:5] Convey("地址相同", func() { header1 := (*reflect.StringHeader)(unsafe.Pointer(&str1)) header2 := (*reflect.StringHeader)(unsafe.Pointer(&str2)) fmt.Println(header1.Data, str1) fmt.Println(header2.Data, str2) So(str1, ShouldNotEqual, str2) So(header1.Data, ShouldEqual, header2.Data) }) })
相同字符串的不同子串,不會額外申請新的內(nèi)存,但是要注意的是這里的相同字符串,指的是 str1.Data == str2.Data && str1.Len == str2.Len
,而不是 str1 == str2
,下面這個例子可以說明 str1 == str2
但是其 Data 并不相同
Convey("不同字符串的相同子串", func() { str1 := "hello world"[:5] str2 := "hello golang"[:5] Convey("地址不同", func() { header1 := (*reflect.StringHeader)(unsafe.Pointer(&str1)) header2 := (*reflect.StringHeader)(unsafe.Pointer(&str2)) fmt.Println(header1.Data, str1) fmt.Println(header2.Data, str2) So(str1, ShouldEqual, str2) So(header1.Data, ShouldNotEqual, header2.Data) }) })
上述就是小編為大家分享的golang 中slice和string如何使用了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。