最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go結構體SliceHeader及StringHeader作用詳解

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

引言

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

今天要給大家介紹的是 SliceHeader 和 StringHeader 結構體,了解清楚他到底是什么,又有什么用,并且會在最后給大家介紹 0 拷貝轉換的內容。

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

SliceHeader

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

SliceHeader 的定義如下:

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

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

在日常程序中,可以利用標準庫 reflect 提供的 SliceHeader 結構體造一個:

func?main()?{
??//?初始化底層數組
?s?:=?[4]string{"腦子",?"進",?"煎魚",?"了"}
?s1?:=?s[0:1]
?s2?:=?s[:]
??//?構造?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)
}

你認為輸出結果是什么,這兩個新切片會指向同一個底層數組的內存地址嗎?

輸出結果:

1 4 824634330936
4 4 824634330936

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

疑問

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

這其實是 Go 語言本身為了減少內存占用,提高整體的性能才這么設計的。

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

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

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

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

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

StringHeader

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

StringHeader 的定義如下:

type?StringHeader?struct?{
???Data?uintptr
???Len??int
}
  • Data:存放指針,其指向具體的存儲數據的內存區(qū)域。
  • Len:字符串的長度。

可得知 “Hello” 字符串的底層數據如下:

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

底層的存儲示意圖如下:

圖來自網絡

真實演示例子如下:

func?main()?{
?s?:=?"腦子進煎魚了"
?s1?:=?"腦子進煎魚了"
?s2?:=?"腦子進煎魚了"[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)
}

你認為輸出結果是什么,變量 s 和 s1、s2 會指向同一個底層內存空間嗎?

輸出結果:

17608227 
17608227 
17608234

從輸出結果來看,變量 s 和 s1 指向同一個內存地址。變量 s2 雖稍有偏差,但本質上也是指向同一塊。

因為其是字符串的切片操作,是從第 7 位索引開始,因此正好的 17608234-17608227 = 7。也就是三個變量都是指向同一塊內存空間,這是為什么呢?

這是因為在 Go 語言中,字符串都是只讀的,為了節(jié)省內存,相同字面量的字符串通常對應于同一字符串常量,因此指向同一個底層數組

0 拷貝轉換

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

常見轉換代碼如下:

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))
}

但這其實是錯誤的,官方明確表示:

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 字段是一個 uintptr 類型。由于 Go 語言只有值傳遞。

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

應該使用如下轉換方式:

func?main()?{
?s?:=?"腦子進煎魚了"
?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
}

在程序必須保留一個單獨的、正確類型的指向底層數據的指針。

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

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

性能對比:

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

會相當標準的轉換性能會稍快一些,這種強轉也會導致一個小問題。

代碼如下:

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

輸出結果:

18 824633927632

這種強轉其會導致 byte 的切片容量非常大,需要特別注意。一般還是推薦使用標準的 SliceHeader、StringHeader 方式就好了,也便于后來的維護者理解。

總結

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

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

最后我們進一步深入,面向 0 拷貝轉換的場景進行了介紹和性能分析。

參考

以上就是Go結構體SliceHeader及StringHeader作用詳解的詳細內容,更多關于Go結構體SliceHeader StringHeader的資料請關注腳本之家其它相關文章!

相關文章

  • Golang 拷貝Array或Slice的操作

    Golang 拷貝Array或Slice的操作

    這篇文章主要介紹了Golang 拷貝Array或Slice的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • 解決Go中使用seed得到相同隨機數的問題

    解決Go中使用seed得到相同隨機數的問題

    這篇文章主要介紹了Go中使用seed得到相同隨機數的問題,需要的朋友可以參考下
    2019-10-10
  • 詳細介紹Go語言之數組與切片

    詳細介紹Go語言之數組與切片

    這篇文章介紹Go語言之數組與切片,數組是具有相同唯一類型的一組已編號且長度固定的數據項序列,這種類型可是任意的原始類型如整形、字符串或自定義類型。切片是數組的一個引用,因此切片是引用類型,在進行傳遞時,遵守引用傳遞的機制,下面我們就來詳細了解一下該內容
    2021-10-10
  • Go打包二進制文件的實現(xiàn)

    Go打包二進制文件的實現(xiàn)

    這篇文章主要介紹了Go打包二進制文件的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-03-03
  • GoLand一鍵上傳項目到遠程服務器的方法步驟

    GoLand一鍵上傳項目到遠程服務器的方法步驟

    我們開發(fā)項目常常將項目上傳到linux遠程服務器上來運行,本文主要介紹了GoLand一鍵上傳項目到遠程服務器的方法步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-06-06
  • Go項目在linux服務器的部署詳細步驟

    Go項目在linux服務器的部署詳細步驟

    在今天的軟件開發(fā)中,使用Linux作為操作系統(tǒng)的比例越來越高,而Golang語言則因為其高效、簡潔和并發(fā)性能等特點,也被越來越多的開發(fā)者所青睞,這篇文章主要給大家介紹了關于Go項目在linux服務器的部署詳細步驟,需要的朋友可以參考下
    2023-09-09
  • Golang程序漏洞檢測器govulncheck的安裝和使用

    Golang程序漏洞檢測器govulncheck的安裝和使用

    govulncheck 是一個命令行工具,可以幫助 Golang 開發(fā)者快速找到項目代碼和依賴的模塊中的安全漏洞,該工具可以分析源代碼和二進制文件,識別代碼中對這些漏洞的任何直接或間接調用,本文就給大家介紹一下govulncheck安裝和使用,需要的朋友可以參考下
    2023-09-09
  • 一文帶大家了解Go語言中的內聯(lián)優(yōu)化

    一文帶大家了解Go語言中的內聯(lián)優(yōu)化

    內聯(lián)優(yōu)化是一種常見的編譯器優(yōu)化策略,通俗來講,就是把函數在它被調用的地方展開,這樣可以減少函數調用所帶來的開銷,本文主要為大家介紹了Go中內聯(lián)優(yōu)化的具體使用,需要的可以參考下
    2023-05-05
  • 一文帶你了解Go語言fmt標準庫輸入函數的使用

    一文帶你了解Go語言fmt標準庫輸入函數的使用

    這篇文章主要為大家詳細介紹了Go語言中?fmt?標準庫輸入函數的使用,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起了解一下
    2023-01-01
  • 掌握GoLang Fiber路由和中間件技術進行高效Web開發(fā)

    掌握GoLang Fiber路由和中間件技術進行高效Web開發(fā)

    這篇文章主要為大家介紹了GoLang Fiber路由和中間件進行高效Web開發(fā),本文將深入探討 Fiber 中的路由細節(jié),學習如何創(chuàng)建和處理路由,深入了解使用路由參數的動態(tài)路由,并掌握在 Fiber 應用程序中實現(xiàn)中間件的藝術
    2024-01-01

最新評論

乐平市| 修武县| 高碑店市| 油尖旺区| 密山市| 喀喇| 固镇县| 汉中市| 依兰县| 大同县| 汾西县| 清原| 紫阳县| 荔波县| 长宁县| 上犹县| 定远县| 丽江市| 长丰县| 武山县| 静宁县| 榆树市| 化州市| 天津市| 淅川县| 石家庄市| 尤溪县| 兴海县| 贺州市| 务川| 华阴市| 高青县| 宜章县| 鄂州市| 礼泉县| 海晏县| 双城市| 万山特区| 绥芬河市| 即墨市| 阿拉善右旗|