亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Go結(jié)構(gòu)體SliceHeader及StringHeader作用詳解

 更新時(shí)間:2022年07月14日 11:08:55   作者:煎魚  
這篇文章主要為大家介紹了Go結(jié)構(gòu)體SliceHeader及StringHeader作用的功能及面試官愛問的實(shí)際意義詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

在 Go 語言中總是有一些看上去奇奇怪怪的東西,咋一眼一看感覺很熟悉,但又不理解其在 Go 代碼中的實(shí)際意義,面試官卻愛問...

今天要給大家介紹的是 SliceHeader 和 StringHeader 結(jié)構(gòu)體,了解清楚他到底是什么,又有什么用,并且會(huì)在最后給大家介紹 0 拷貝轉(zhuǎn)換的內(nèi)容。

一起愉快地開始吸魚之路。

SliceHeader

SliceHeader 如其名,Slice + Header,看上去很直觀,實(shí)際上是 Go Slice(切片)的運(yùn)行時(shí)表現(xiàn)。

SliceHeader 的定義如下:

type?SliceHeader?struct?{
?Data?uintptr
?Len??int
?Cap??int
}
  • Data:指向具體的底層數(shù)組。
  • Len:代表切片的長(zhǎng)度。
  • Cap:代表切片的容量。

既然知道了切片的運(yùn)行時(shí)表現(xiàn),那是不是就意味著我們可以自己造一個(gè)?

在日常程序中,可以利用標(biāo)準(zhǔn)庫 reflect 提供的 SliceHeader 結(jié)構(gòu)體造一個(gè):

func?main()?{
??//?初始化底層數(shù)組
?s?:=?[4]string{"腦子",?"進(jìn)",?"煎魚",?"了"}
?s1?:=?s[0:1]
?s2?:=?s[:]
??//?構(gòu)造?SliceHeader
?sh1?:=?(*reflect.SliceHeader)(unsafe.Pointer(&s1))
?sh2?:=?(*reflect.SliceHeader)(unsafe.Pointer(&s2))
?fmt.Println(sh1.Len,?sh1.Cap,?sh1.Data)
?fmt.Println(sh2.Len,?sh2.Cap,?sh2.Data)
}

你認(rèn)為輸出結(jié)果是什么,這兩個(gè)新切片會(huì)指向同一個(gè)底層數(shù)組的內(nèi)存地址嗎?

輸出結(jié)果:

1 4 824634330936
4 4 824634330936

兩個(gè)切片的 Data 屬性所指向的底層數(shù)組是一致的,Len 屬性的值不一樣,sh1 和 sh2 分別是兩個(gè)切片。

疑問

為什么兩個(gè)新切片所指向的 Data 是同一個(gè)地址的呢?

這其實(shí)是 Go 語言本身為了減少內(nèi)存占用,提高整體的性能才這么設(shè)計(jì)的。

將切片復(fù)制到任意函數(shù)的時(shí)候,對(duì)底層數(shù)組大小都不會(huì)影響。復(fù)制時(shí)只會(huì)復(fù)制切片本身(值傳遞),不會(huì)涉及底層數(shù)組。

也就是在函數(shù)間傳遞切片,其只拷貝 24 個(gè)字節(jié)(指針字段 8 個(gè)字節(jié),長(zhǎng)度和容量分別需要 8 個(gè)字節(jié)),效率很高。

這種設(shè)計(jì)也引出了新的問題,在平時(shí)通過 s[i:j] 所生成的新切片,兩個(gè)切片底層指向的是同一個(gè)底層數(shù)組。

假設(shè)在沒有超過容量(cap)的情況下,對(duì)第二個(gè)切片操作會(huì)影響第一個(gè)切片。

這是很多 Go 開發(fā)常會(huì)碰到的一個(gè)大 “坑”,不清楚的排查了很久的都不得而終。

StringHeader

除了 SliceHeader 外,Go 語言中還有一個(gè)典型代表,那就是字符串(string)的運(yùn)行時(shí)表現(xiàn)。

StringHeader 的定義如下:

type?StringHeader?struct?{
???Data?uintptr
???Len??int
}
  • Data:存放指針,其指向具體的存儲(chǔ)數(shù)據(jù)的內(nèi)存區(qū)域。
  • Len:字符串的長(zhǎng)度。

可得知 “Hello” 字符串的底層數(shù)據(jù)如下:

var?data?=?[...]byte{
????'h',?'e',?'l',?'l',?'o',
}

底層的存儲(chǔ)示意圖如下:

圖來自網(wǎng)絡(luò)

真實(shí)演示例子如下:

func?main()?{
?s?:=?"腦子進(jìn)煎魚了"
?s1?:=?"腦子進(jìn)煎魚了"
?s2?:=?"腦子進(jìn)煎魚了"[7:]
?fmt.Printf("%d?\n",?(*reflect.StringHeader)(unsafe.Pointer(&s)).Data)
?fmt.Printf("%d?\n",?(*reflect.StringHeader)(unsafe.Pointer(&s1)).Data)
?fmt.Printf("%d?\n",?(*reflect.StringHeader)(unsafe.Pointer(&s2)).Data)
}

你認(rèn)為輸出結(jié)果是什么,變量 s 和 s1、s2 會(huì)指向同一個(gè)底層內(nèi)存空間嗎?

輸出結(jié)果:

17608227 
17608227 
17608234

從輸出結(jié)果來看,變量 s 和 s1 指向同一個(gè)內(nèi)存地址。變量 s2 雖稍有偏差,但本質(zhì)上也是指向同一塊。

因?yàn)槠涫亲址那衅僮?,是從?7 位索引開始,因此正好的 17608234-17608227 = 7。也就是三個(gè)變量都是指向同一塊內(nèi)存空間,這是為什么呢?

這是因?yàn)樵?Go 語言中,字符串都是只讀的,為了節(jié)省內(nèi)存,相同字面量的字符串通常對(duì)應(yīng)于同一字符串常量,因此指向同一個(gè)底層數(shù)組

0 拷貝轉(zhuǎn)換

為什么會(huì)有人關(guān)注到 SliceHeader、StringHeader 這類運(yùn)行時(shí)細(xì)節(jié)呢,一大部分原因是業(yè)內(nèi)會(huì)有開發(fā)者,希望利用其實(shí)現(xiàn)零拷貝的 string 到 bytes 的轉(zhuǎn)換。

常見轉(zhuǎn)換代碼如下:

func?string2bytes(s?string)?[]byte?{
?stringHeader?:=?(*reflect.StringHeader)(unsafe.Pointer(&s))
?bh?:=?reflect.SliceHeader{
??Data:?stringHeader.Data,
??Len:??stringHeader.Len,
??Cap:??stringHeader.Len,
?}
?return?*(*[]byte)(unsafe.Pointer(&bh))
}

但這其實(shí)是錯(cuò)誤的,官方明確表示:

the Data field is not sufficient to guarantee the data it references will not be garbage collected, so programs must keep a separate, correctly typed pointer to the underlying data.

SliceHeader、StringHeader 的 Data 字段是一個(gè) uintptr 類型。由于 Go 語言只有值傳遞。

因此在上述代碼中會(huì)出現(xiàn)將 Data 作為值拷貝的情況,這就會(huì)導(dǎo)致無法保證它所引用的數(shù)據(jù)不會(huì)被垃圾回收(GC)

應(yīng)該使用如下轉(zhuǎn)換方式:

func?main()?{
?s?:=?"腦子進(jìn)煎魚了"
?v?:=?string2bytes1(s)
?fmt.Println(v)
}
func?string2bytes1(s?string)?[]byte?{
?stringHeader?:=?(*reflect.StringHeader)(unsafe.Pointer(&s))
?var?b?[]byte
?pbytes?:=?(*reflect.SliceHeader)(unsafe.Pointer(&b))
?pbytes.Data?=?stringHeader.Data
?pbytes.Len?=?stringHeader.Len
?pbytes.Cap?=?stringHeader.Len
?return?b
}

在程序必須保留一個(gè)單獨(dú)的、正確類型的指向底層數(shù)據(jù)的指針。

在性能方面,若只是期望單純的轉(zhuǎn)換,對(duì)容量(cap)等字段值不敏感,也可以使用以下方式:

func?string2bytes2(s?string)?[]byte?{
?return?*(*[]byte)(unsafe.Pointer(&s))
}

性能對(duì)比:

string2bytes1-1000-4???3.746?ns/op??0?allocs/op
string2bytes1-1000-4???3.713?ns/op??0?allocs/op
string2bytes1-1000-4???3.969?ns/op??0?allocs/op
string2bytes2-1000-4???2.445?ns/op??0?allocs/op
string2bytes2-1000-4???2.451?ns/op??0?allocs/op
string2bytes2-1000-4???2.455?ns/op??0?allocs/op

會(huì)相當(dāng)標(biāo)準(zhǔn)的轉(zhuǎn)換性能會(huì)稍快一些,這種強(qiáng)轉(zhuǎn)也會(huì)導(dǎo)致一個(gè)小問題。

代碼如下:

func?main()?{
?s?:=?"腦子進(jìn)煎魚了"
?v?:=?string2bytes2(s)
?println(len(v),?cap(v))
}
func?string2bytes2(s?string)?[]byte?{
?return?*(*[]byte)(unsafe.Pointer(&s))
}

輸出結(jié)果:

18 824633927632

這種強(qiáng)轉(zhuǎn)其會(huì)導(dǎo)致 byte 的切片容量非常大,需要特別注意。一般還是推薦使用標(biāo)準(zhǔn)的 SliceHeader、StringHeader 方式就好了,也便于后來的維護(hù)者理解。

總結(jié)

在這篇文章中,我們介紹了字符串(string)和切片(slice)的兩個(gè)運(yùn)行時(shí)表現(xiàn),分別是 StringHeader 和 SliceHeader。

同時(shí)了解到其運(yùn)行時(shí)表現(xiàn)后,我們還針對(duì)其兩者的地址指向,常見坑進(jìn)行了說明。

最后我們進(jìn)一步深入,面向 0 拷貝轉(zhuǎn)換的場(chǎng)景進(jìn)行了介紹和性能分析。

參考

以上就是Go結(jié)構(gòu)體SliceHeader及StringHeader作用詳解的詳細(xì)內(nèi)容,更多關(guān)于Go結(jié)構(gòu)體SliceHeader StringHeader的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang中的深拷貝與淺拷貝使用

    Golang中的深拷貝與淺拷貝使用

    本文主要介紹了Golang中的深拷貝與淺拷貝使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • golang使用viper加載配置文件實(shí)現(xiàn)自動(dòng)反序列化到結(jié)構(gòu)

    golang使用viper加載配置文件實(shí)現(xiàn)自動(dòng)反序列化到結(jié)構(gòu)

    這篇文章主要為大家介紹了golang使用viper加載配置文件實(shí)現(xiàn)自動(dòng)反序列化到結(jié)構(gòu)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08
  • golang定時(shí)器和超時(shí)的使用詳解

    golang定時(shí)器和超時(shí)的使用詳解

    這篇文章主要介紹了golang定時(shí)器和超時(shí)的使用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • GoLang RabbitMQ實(shí)現(xiàn)六種工作模式示例

    GoLang RabbitMQ實(shí)現(xiàn)六種工作模式示例

    這篇文章主要介紹了GoLang RabbitMQ實(shí)現(xiàn)六種工作模式,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-12-12
  • Golang 1.16 中 Modules的主要變化更新

    Golang 1.16 中 Modules的主要變化更新

    這篇文章主要介紹了Golang 1.16 中 Modules的主要變化更新,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-02-02
  • Golang 編譯成DLL文件的操作

    Golang 編譯成DLL文件的操作

    這篇文章主要介紹了Golang 編譯成DLL文件的操作方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • go語言beego框架分頁器操作及接口頻率限制示例

    go語言beego框架分頁器操作及接口頻率限制示例

    這篇文章主要為大家介紹了go語言beego框架分頁器操作使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-04-04
  • Goland 2020或2019軟件版本去掉a...或fmt...提示的方法

    Goland 2020或2019軟件版本去掉a...或fmt...提示的方法

    這篇文章主要介紹了Goland 2020或2019軟件版本去掉a...或fmt...提示的方法,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-10-10
  • go語言算法題解二叉樹的最小深度

    go語言算法題解二叉樹的最小深度

    這篇文章主要為大家介紹了go語言算法題解二叉樹的最小深度示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • Go語言使用defer+recover解決panic導(dǎo)致程序崩潰的問題

    Go語言使用defer+recover解決panic導(dǎo)致程序崩潰的問題

    如果協(xié)程出現(xiàn)了panic,就會(huì)造成程序的崩潰,這時(shí)可以在goroutine中使用recover來捕獲panic,進(jìn)行處理,本文就詳細(xì)的介紹一下,感興趣的可以了解一下
    2021-09-09

最新評(píng)論