版權(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)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 昆山鈔票紙業(yè)有限公司2026年度招聘備考題庫附答案詳解
- 2025年四川大學(xué)華西樂城醫(yī)院招聘18人備考題庫有答案詳解
- 2025年哈爾濱市天元學(xué)校招聘臨聘教師備考題庫及答案詳解參考
- 2025年蒙晟建設(shè)有限公司公開招聘緊缺專業(yè)人員的備考題庫及完整答案詳解1套
- 2025年四川省筠連縣公證處公開招聘公證員2人備考題庫及一套參考答案詳解
- 功能性腹脹中醫(yī)診療專家共識總結(jié)2026
- 漸變風(fēng)年會慶典晚會表彰
- 《植物工廠多層立體栽培模式光環(huán)境調(diào)控與植物生長周期調(diào)控研究》教學(xué)研究課題報(bào)告
- 2025年張家港市第三人民醫(yī)院自主招聘編外合同制衛(wèi)技人員備考題庫附答案詳解
- 2025年浙江省中醫(yī)院、浙江中醫(yī)藥大學(xué)附屬第一醫(yī)院(第一臨床醫(yī)學(xué)院)公開招聘人員備考題庫及一套完整答案詳解
- 2025年國家開放大學(xué)管理英語3作業(yè)答案
- 乳腺癌全程、全方位管理乳腺癌患者依從性及心理健康管理幻燈
- 四川省高職單招汽車類《汽車機(jī)械基礎(chǔ)》復(fù)習(xí)備考試題庫(含答案)
- 2024CSCO腫瘤患者靜脈血栓防治指南解讀
- MOOC 中國文化概論-華南師范大學(xué) 中國大學(xué)慕課答案
- 博物館保安服務(wù)投標(biāo)方案(技術(shù)方案)
- 浙人美版美術(shù)五年級上冊期末復(fù)習(xí)資料整理
- 年產(chǎn)20萬噸氯乙烯工藝設(shè)計(jì)
- GB/T 42737-2023電化學(xué)儲能電站調(diào)試規(guī)程
- 人民網(wǎng)輿情監(jiān)測室發(fā)布2023年互聯(lián)網(wǎng)輿情分析報(bào)告
- 博士論文的寫作
評論
0/150
提交評論