Go結(jié)構(gòu)體SliceHeader及StringHeader作用詳解_第1頁
Go結(jié)構(gòu)體SliceHeader及StringHeader作用詳解_第2頁
Go結(jié)構(gòu)體SliceHeader及StringHeader作用詳解_第3頁
Go結(jié)構(gòu)體SliceHeader及StringHeader作用詳解_第4頁
Go結(jié)構(gòu)體SliceHeader及StringHeader作用詳解_第5頁
已閱讀5頁,還剩3頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

第Go結(jié)構(gòu)體SliceHeader及StringHeader作用詳解目錄引言SliceHeader疑問坑StringHeader0拷貝轉(zhuǎn)換總結(jié)

引言

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

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

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

SliceHeader

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

SliceHeader的定義如下:

type

SliceHeader

struct

{

Data

uintptr

Len

int

Cap

int

}

Data:指向具體的底層數(shù)組。Len:代表切片的長度。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(amp;s1))

sh2

:=

(*reflect.SliceHeader)(unsafe.Pointer(amp;s2))

fmt.Println(sh1.Len,

sh1.Cap,

sh1.Data)

fmt.Println(sh2.Len,

sh2.Cap,

sh2.Data)

}

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

輸出結(jié)果:

14824634330936

44824634330936

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

疑問

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

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

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

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

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

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

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

StringHeader

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

StringHeader的定義如下:

type

StringHeader

struct

{

Data

uintptr

Len

int

}

Data:存放指針,其指向具體的存儲數(shù)據(jù)的內(nèi)存區(qū)域。Len:字符串的長度。

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

var

data

=

[...]byte{

'h',

'e',

'l',

'l',

'o',

}

底層的存儲示意圖如下:

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

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

func

main()

{

s

:=

"腦子進(jìn)煎魚了"

s1

:=

"腦子進(jìn)煎魚了"

s2

:=

"腦子進(jìn)煎魚了"[7:]

fmt.Printf("%d

\n",

(*reflect.StringHeader)(unsafe.Pointer(amp;s)).Data)

fmt.Printf("%d

\n",

(*reflect.StringHeader)(unsafe.Pointer(amp;s1)).Data)

fmt.Printf("%d

\n",

(*reflect.StringHeader)(unsafe.Pointer(amp;s2)).Data)

}

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

輸出結(jié)果:

17608227

17608227

17608234

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

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

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

0拷貝轉(zhuǎn)換

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

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

func

string2bytes(s

string)

[]byte

{

stringHeader

:=

(*reflect.StringHeader)(unsafe.Pointer(amp;s))

bh

:=

reflect.SliceHeader{

Data:

stringHeader.Data,

Len:

stringHeader.Len,

Cap:

stringHeader.Len,

return

*(*[]byte)(unsafe.Pointer(amp;bh))

}

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

theDatafieldisnotsufficienttoguaranteethedataitreferenceswillnotbegarbagecollected,soprogramsmustkeepaseparate,correctlytypedpointertotheunderlyingdata.

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

因此在上述代碼中會出現(xiàn)將Data作為值拷貝的情況,這就會導(dǎo)致無法保證它所引用的數(shù)據(jù)不會被垃圾回收(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(amp;s))

var

b

[]byte

pbytes

:=

(*reflect.SliceHeader)(unsafe.Pointer(amp;b))

pbytes.Data

=

stringHeader.Data

pbytes.Len

=

stringHeader.Len

pbytes.Cap

=

stringHeader.Len

return

b

}

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

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

func

string2bytes2(s

string)

[]byte

{

return

*(*[]byte)(unsafe.Pointer(amp;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

會相當(dāng)標(biāo)準(zhǔn)的轉(zhuǎn)換性能會稍快一些,這種強(qiáng)轉(zhuǎn)也會導(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(amp;s))

}

輸出結(jié)果:

18824633927632

這種強(qiáng)轉(zhuǎn)其會導(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)

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論