日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
搞Go要了解的2個Header,你知道嗎?

本文轉(zhuǎn)載自微信公眾號「腦子進煎魚了」,作者陳煎魚。轉(zhuǎn)載本文請聯(lián)系腦子進煎魚了公眾號。

成都創(chuàng)新互聯(lián)公司主要從事網(wǎng)站制作、成都網(wǎng)站制作、網(wǎng)頁設計、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務。立足成都服務新民,十年網(wǎng)站建設經(jīng)驗,價格優(yōu)惠、服務專業(yè),歡迎來電咨詢建站服務:13518219792

大家好,我是煎魚。

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

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

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

SliceHeader

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

 
 
 
 
  1. type SliceHeader struct { 
  2.  Data uintptr 
  3.  Len  int 
  4.  Cap  int 
  • Data:指向具體的底層數(shù)組。
  • Len:代表切片的長度。
  • Cap:代表切片的容量。

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

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

 
 
 
 
  1. func main() { 
  2.   // 初始化底層數(shù)組 
  3.  s := [4]string{"腦子", "進", "煎魚", "了"} 
  4.  s1 := s[0:1] 
  5.  s2 := s[:] 
  6.  
  7.   // 構造 SliceHeader 
  8.  sh1 := (*reflect.SliceHeader)(unsafe.Pointer(&s1)) 
  9.  sh2 := (*reflect.SliceHeader)(unsafe.Pointer(&s2)) 
  10.  fmt.Println(sh1.Len, sh1.Cap, sh1.Data) 
  11.  fmt.Println(sh2.Len, sh2.Cap, sh2.Data) 

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

輸出結果:

 
 
 
 
  1. 1 4 824634330936 
  2.  
  3. 4 4 824634330936 

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

疑問

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

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

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

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

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

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

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

StringHeader

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

 
 
 
 
  1. type StringHeader struct { 
  2.    Data uintptr 
  3.    Len  int 
  • Data:存放指針,其指向具體的存儲數(shù)據(jù)的內(nèi)存區(qū)域。
  • Len:字符串的長度。

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

 
 
 
 
  1. var data = [...]byte{ 
  2.     'h', 'e', 'l', 'l', 'o', 

底層的存儲示意圖如下:

真實演示例子如下:

 
 
 
 
  1. func main() { 
  2.  s := "腦子進煎魚了" 
  3.  s1 := "腦子進煎魚了" 
  4.  s2 := "腦子進煎魚了"[7:] 
  5.  
  6.  fmt.Printf("%d \n", (*reflect.StringHeader)(unsafe.Pointer(&s)).Data) 
  7.  fmt.Printf("%d \n", (*reflect.StringHeader)(unsafe.Pointer(&s1)).Data) 
  8.  fmt.Printf("%d \n", (*reflect.StringHeader)(unsafe.Pointer(&s2)).Data) 

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

輸出結果:

 
 
 
 
  1. 17608227  
  2. 17608227  
  3. 17608234  

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

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

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

0 拷貝轉(zhuǎn)換

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

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

 
 
 
 
  1. func string2bytes(s string) []byte { 
  2.  stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&s)) 
  3.  
  4.  bh := reflect.SliceHeader{ 
  5.   Data: stringHeader.Data, 
  6.   Len:  stringHeader.Len, 
  7.   Cap:  stringHeader.Len, 
  8.  } 
  9.  
  10.  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 作為值拷貝的情況,這就會導致無法保證它所引用的數(shù)據(jù)不會被垃圾回收(GC)。

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

 
 
 
 
  1. func main() { 
  2.  s := "腦子進煎魚了" 
  3.  v := string2bytes1(s) 
  4.  fmt.Println(v) 
  5.  
  6. func string2bytes1(s string) []byte { 
  7.  stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&s)) 
  8.  
  9.  var b []byte 
  10.  pbytes := (*reflect.SliceHeader)(unsafe.Pointer(&b)) 
  11.  pbytes.Data = stringHeader.Data 
  12.  pbytes.Len = stringHeader.Len 
  13.  pbytes.Cap = stringHeader.Len 
  14.  
  15.  return b 

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

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

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

性能對比:

 
 
 
 
  1. string2bytes1-1000-4   3.746 ns/op  0 allocs/op 
  2. string2bytes1-1000-4   3.713 ns/op  0 allocs/op 
  3. string2bytes1-1000-4   3.969 ns/op  0 allocs/op 
  4.  
  5. string2bytes2-1000-4   2.445 ns/op  0 allocs/op 
  6. string2bytes2-1000-4   2.451 ns/op  0 allocs/op 
  7. string2bytes2-1000-4   2.455 ns/op  0 allocs/op 

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

代碼如下:

 
 
 
 
  1. func main() { 
  2.  s := "腦子進煎魚了" 
  3.  v := string2bytes2(s) 
  4.  println(len(v), cap(v)) 
  5. func string2bytes2(s string) []byte { 
  6.  return *(*[]byte)(unsafe.Pointer(&s)) 

輸出結果:

 
 
 
 
  1. 18 824633927632 

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

總結

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

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

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

你平時有沒有遇到過這塊的疑惑或問題呢,歡迎大家一起討論!

參考

Go語言slice的本質(zhì)-SliceHeader

數(shù)組、字符串和切片

零拷貝實現(xiàn)string 和bytes的轉(zhuǎn)換疑問


本文名稱:搞Go要了解的2個Header,你知道嗎?
標題網(wǎng)址:http://www.5511xx.com/article/cdoceod.html