深入string理解Golang是怎樣實(shí)現(xiàn)的_第1頁
深入string理解Golang是怎樣實(shí)現(xiàn)的_第2頁
深入string理解Golang是怎樣實(shí)現(xiàn)的_第3頁
深入string理解Golang是怎樣實(shí)現(xiàn)的_第4頁
深入string理解Golang是怎樣實(shí)現(xiàn)的_第5頁
已閱讀5頁,還剩7頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

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

文檔簡介

第深入string理解Golang是怎樣實(shí)現(xiàn)的目錄引言內(nèi)容介紹字符串?dāng)?shù)據(jù)結(jié)構(gòu)字符串會分配到內(nèi)存中的哪塊區(qū)域編譯期即可確定的字符串如果我們創(chuàng)建兩個helloworld字符串,他們會放到同一內(nèi)存區(qū)域嗎運(yùn)行時通過+拼接的字符串會放到那塊內(nèi)存中字面量是否會在編譯器合并當(dāng)我們用+連接多個字符串時,會發(fā)生什么rawstring函數(shù)go中字符串是不可變的嗎,我們?nèi)绾蔚玫揭粋€可變的字符串[]byte和string的更高效轉(zhuǎn)換結(jié)尾

引言

本身打算先寫完sync包的,但前幾天在復(fù)習(xí)以前筆記的時候突然發(fā)現(xiàn)與字符串相關(guān)的寥寥無幾.同時作為一個Java選手,很輕易的想到了幾個問題

go字符串存儲于內(nèi)存的哪部分區(qū)域我們初始化兩個helloworld,這兩個helloworld會放到同一塊內(nèi)存空間嗎go字符串是動態(tài)的還是靜態(tài)的,修改他的時候是修改原字符串還是新構(gòu)建一個字符串

在網(wǎng)上搜索后發(fā)現(xiàn)目前網(wǎng)上對go語言字符串的介紹相關(guān)甚少,因此我在仔細(xì)閱讀源碼后產(chǎn)出了這批文章.

ps:本文雖由Java中問題引出,但后續(xù)內(nèi)容和Java無關(guān),碼字不易,對你有幫助的話麻煩幫忙點(diǎn)個贊^_^.

內(nèi)容介紹

本文將介紹如下內(nèi)容

字符串?dāng)?shù)據(jù)結(jié)構(gòu)

字符串中的數(shù)據(jù)結(jié)構(gòu)如下

typestringStructstruct{

strunsafe.Pointer

lenint

str:大部分情況下指向只讀數(shù)據(jù)段中的一塊內(nèi)存區(qū)域,少部分情況指向堆/棧,unsafe.Pointer類型,大小8字節(jié).len:這個字符串的長度,int類型,在64bit機(jī)上大小8字節(jié),在32bit機(jī)上大小4字節(jié).

字符串會分配到內(nèi)存中的哪塊區(qū)域

我們先看下這張圖,下面內(nèi)容結(jié)合本圖理解

我們把字符串分為兩種

編譯期即可確定的字符串,如a:=hello運(yùn)行時通過+拼接得到的字符串,如b:=a+world

編譯期即可確定的字符串

如a:=helloworld

我們這里把字符串占用的內(nèi)存分為兩部分

stringStruct結(jié)構(gòu)體所在的內(nèi)存unsafe.Pointer類型的str所在的內(nèi)存

首先是stringStruct,他是一個16字節(jié)大小的結(jié)構(gòu)體,因此他和一個普通結(jié)構(gòu)體一樣,根據(jù)逃逸分析判斷是否可以分配在棧上,如果不行,也會根據(jù)分級分配的方式分配到堆中.

而str則是指向了.rodata(只讀數(shù)據(jù)段)中的存放的字符串字面量,因此字符串字面量是在.rodata中

綜上:string的數(shù)據(jù)結(jié)構(gòu)stringStruct分配在堆/棧中,而他對應(yīng)的字符串字面量則是在只讀數(shù)據(jù)段中

如果我們創(chuàng)建兩個helloworld字符串,他們會放到同一內(nèi)存區(qū)域嗎

根據(jù)上面的分析,我們可以很容易的得到答案,他們的數(shù)據(jù)結(jié)構(gòu)stringStruct會分配在堆/棧的不同內(nèi)存空間中,而unsafe.Pointer則指向.rodata中的同一塊內(nèi)存區(qū)域

我們可以做出如下驗(yàn)證方式

//因?yàn)閟tringStruct是runtime包下一個不對外暴露的數(shù)據(jù)結(jié)構(gòu),

//所以我們新建一個結(jié)構(gòu)相同的數(shù)據(jù)結(jié)構(gòu)來接收string的內(nèi)容

typeReceptionstruct{

punsafe.Pointer

lenint

funcmain(){

a:="helloworld"

b:="helloworld"

//用新建的Reception接收字符串內(nèi)容,本質(zhì)上就是把a(bǔ)/b對應(yīng)的二進(jìn)制數(shù)據(jù)重新解析為Reception,

//而Reception和stringStruct的結(jié)構(gòu)相同,所以不會出問題.

rA:=*(*Reception)(unsafe.Pointer(a))

rB:=*(*Reception)(unsafe.Pointer(b))

//輸出a,b的地址

fmt.Println(a)

fmt.Println(b)

//輸出stringStruct的str指向的地址

fmt.Println(rA.p)

fmt.Println(rB.p)

我們得到了如下結(jié)果

0xc000050260

0xc000050270

0x595700

0x595700

a,b兩個stringStruct被分配到不同地址,而他們的str則指向了同一地址.

運(yùn)行時通過+拼接的字符串會放到那塊內(nèi)存中

字面量是否會在編譯器合并

funcmain(){

he:="hello"

//編譯期"li","hua"未能合并

str1:=he+"li"+"hua"

//編譯期被合并為"nihao"

str2:="ni"+"hao"

fmt.Println(str1)

網(wǎng)上有的文章說,字符串字面量會在編譯期進(jìn)行合并,但我在SDK1.18.9下測試的結(jié)果是只有右值為純字面量時,才會合并.

我們使用gotoolcompile-mmain.go命令分析,結(jié)果如下

main.go:8:13:inliningcalltofmt.Println

//如果合并的話,應(yīng)該是he+"lihua"

main.go:7:17:he+"li"+"hua"escapestoheap

main.go:8:13:...argumentdoesnotescape

main.go:8:13:str1escapestoheap

大家可以自己用上述命令分析下自己SDK版本是否會合并.

不過重要的是,我們知道右值為純字面量拼接的字符串會在編譯期合并,等價于右值為純字面量的字符串,他的分配方式和編譯期可確定的字符串一致.

接下來我們討論右值表達(dá)式中存在變量的情況下是如何進(jìn)行內(nèi)存分配的

當(dāng)我們用+連接多個字符串時,會發(fā)生什么

我們先說結(jié)論,運(yùn)行時通過+連接多個字符串構(gòu)成新串,新串的stringStruct結(jié)構(gòu)體和str指向的字面量都會被分配到堆/??臻g中.

在go語言編譯期,會把字符串的+替換為funcconcatstrings(buf*tmpBuf,a[]string)string函數(shù).

分配到棧上還是堆上

我們看下concatstrings的兩個參數(shù),其中buf是一個??臻g的內(nèi)存,go語言會通過所有要拼接的字符串總長度以及逃逸分析確定這個字符串會不會分配到棧上,如果要分配到棧上,則會傳來buf參數(shù).

棧上分配和堆上分配的流程幾乎一致,只不過在內(nèi)存分配的時候會根據(jù)buf!=nil來判斷該存放到哪塊內(nèi)存空間而已,因此下文中我們統(tǒng)一按堆分配介紹.

而第二個參數(shù)a中存儲有全部需要通過+連接的字符串

concatstrings函數(shù)執(zhí)行流程如下

用forrange循環(huán)來遍歷整個a數(shù)組,計算其中所有非空串的個數(shù)count和長度總和l然后調(diào)用funcrawstringtmp(buf*tmpBuf,lint)(sstring,b[]byte)函數(shù)來為這個字符串分配內(nèi)存空間,并返回字符串和其底層的[]byte數(shù)組.對于該函數(shù)來說,如果buf!=nil則使用buf的內(nèi)存空間,否則調(diào)用funcrawstring(sizeint)(sstring,b[]byte)函數(shù),rawstring函數(shù)會調(diào)用mallocgc來在堆上分配內(nèi)存空間,并返回使用該內(nèi)存空間的字符串及其底層切片.此時我們已經(jīng)拿到了一個字符串及其底層切片,因?yàn)樽址豢勺?所以go通過修改其底層數(shù)組來為字符串賦值,他會再次forrange循環(huán)a數(shù)組,然后通過copy函數(shù)來把a(bǔ)中的字符串拷貝到新串對應(yīng)的底層數(shù)組b中,從而達(dá)到修改新串的目的.至此,字符串s的內(nèi)存分配和初始化已經(jīng)全部完成,rawstringtmp函數(shù)返回

這樣我們就得到了一個全部內(nèi)存空間都分配在堆/棧中的字符串.

因此,即使運(yùn)行時多個通過+連接而成的新串有著相同的字面量,他們的str也會指向不同的內(nèi)存空間

驗(yàn)證

我們可以繼續(xù)把字符串轉(zhuǎn)換為Reception來看看他的str執(zhí)行的地址

//因?yàn)閟tringStruct是runtime包下一個不對外暴露的數(shù)據(jù)結(jié)構(gòu),

//所以我們新建一個結(jié)構(gòu)相同的數(shù)據(jù)結(jié)構(gòu)來接收string的內(nèi)容

typeReceptionstruct{

punsafe.Pointer

lenint

funcmain(){

h:="hello"

a:=h+"world"

b:=h+"world"

//用新建的Reception接收字符串內(nèi)容,本質(zhì)上就是把a(bǔ)/b對應(yīng)的二進(jìn)制數(shù)據(jù)重新解析為Reception,

//而Reception和stringStruct的結(jié)構(gòu)相同,所以不會出問題.

rA:=*(*Reception)(unsafe.Pointer(a))

rB:=*(*Reception)(unsafe.Pointer(b))

//輸出a,b的地址

fmt.Println(a)

fmt.Println(b)

//輸出stringStruct的str指向的地址

fmt.Println(rA.p)

fmt.Println(rB.p)

結(jié)果如下

0xc000050260

0xc000050270

0xc00000a0e0

0xc00000a0f0

a和b字符串的str字段指向堆中不同的內(nèi)存區(qū)域.

rawstring函數(shù)

rawstring真的是一個十分有趣的函數(shù),因此我決定對他進(jìn)行詳細(xì)的分析,但他相對有點(diǎn)難度,如果靜下心來讀懂,定能讓您有所收獲.我們直接上源碼逐行分析

funcrawstring(sizeint)(sstring,b[]byte){

//在堆中申請內(nèi)存

p:=mallocgc(uintptr(size),nil,false)

//把string轉(zhuǎn)換為stringStruct數(shù)據(jù)結(jié)構(gòu)

stringStructOf(amp;s).str=p

stringStructOf(amp;s).len=size

//最重要的部分,讓b重新指向p空間

*(*slice)(unsafe.Pointer(amp;b))=slice{p,size,size}

return

funcstringStructOf(sp*string)*stringStruct{

return(*stringStruct)(unsafe.Pointer(sp))

stringStructOf函數(shù)十分簡單,因?yàn)閟tring和stringStruct的結(jié)構(gòu)完全相同,因此他直接通過把(*stringStruct)(unsafe.Pointer(sp))來把字符串指針sp轉(zhuǎn)換為stringStruct指針,然后通過stringStruct指針來獲取stringStruct結(jié)構(gòu)體.

我們可以這樣理解下轉(zhuǎn)換方式.

sp是一個string類型的指針,他指向一塊內(nèi)存區(qū)域,這塊內(nèi)存區(qū)域中全是二進(jìn)制bit流,但是我們會安裝string的形式解釋他,即前8位被解釋成一個指針,后8位被解釋成一個int類型.我們把sp轉(zhuǎn)換為一個unsafe.Pointer,此時將只保留起始地址和長度然后我們再把sp轉(zhuǎn)換為stringStruct,因此會按stringStruct的方式解釋這段二進(jìn)制bit流,而因?yàn)閟tringStruct的結(jié)構(gòu)和string一樣,所以也會把前8位解釋成一個指針,后8位解釋成一個int類型,不會出現(xiàn)差錯.

接下來我們按同樣的思路看下*(*slice)(unsafe.Pointer(b))=slice{p,size,size}

首先獲取到b的地址,然后把他轉(zhuǎn)換為一個*slice然后通過取地址運(yùn)算符來獲取slice對應(yīng)的slice又因?yàn)閟lice本身就是指針類型,所以我們讓這個slice=slice{p,size,size}的時候只是改變了其指向,也就等價于讓b改變指向,使其指向p這塊內(nèi)存空間,也就是str指向的那塊內(nèi)存空間.

只會我們就可以通過b來修改這塊內(nèi)存空間,從而間接修改字符串的ne

go中字符串是不可變的嗎,我們?nèi)绾蔚玫揭粋€可變的字符串

go中字符串在語義中是不可變的,并且咱們對字符串進(jìn)行+操作時也是新開辟一塊內(nèi)存空間來存放修改后的字符串,真的沒有什么辦法改變一個字符串中的數(shù)據(jù)嗎

回顧下我們之前分析的結(jié)論

對于編譯期確定的字符串,他的str指針指向一個.rodata區(qū)的字面量,不會被改變.而運(yùn)行時確定的字符串,他的str指針指向一個堆棧中的空間,我們可以讓一個[]byte指向其底層內(nèi)存空間從而間接改變其內(nèi)容

對于編譯期確定的字符串,嘗試修改.rodata區(qū)中的字面量會panic

//嘗試修改.rodata區(qū)中數(shù)據(jù),painic

funcmain(){

str:="helloworld"

byteArr:=*(*[]byte)(unsafe.Pointer(str))

byteArr[0]='w'

fmt.Println(str)

而對于運(yùn)行時通過+拼接得到的新串,修改堆棧中存放的字面量則可以成功

//輸出welloworld

funcmain(){

str:="hello"

//此時字符串str的unsafe.Pointer指針str會重新指向堆中內(nèi)存

str+="world"

//讓[]byte也指向堆中內(nèi)存

byteArr:=*(*[]byte)(unsafe.Pointer(str))

//修改

byteArr[0]='w'

fmt.Println(str)

[]byte和string的更高效轉(zhuǎn)換

一般情況下我們使用的強(qiáng)制類型的方式進(jìn)行[]byte和string的互相轉(zhuǎn)換都會被替換為stringtoslicebyte和slicebytetostring函數(shù),這兩個函數(shù)都會新申請一個內(nèi)存空間,然后將原本[]byte或string中的數(shù)據(jù)拷貝到新內(nèi)存空間中,涉及一次內(nèi)存copy.

我們可以采用unsafe.Pointer當(dāng)作一個中介來進(jìn)行更高效的類型轉(zhuǎn)換,事實(shí)上,這個方式咱們之前已多次使用.

string-byte[]

funcmain(){

str:="hello"

//注意下面這一行,是核心

byteArr:=*(*[]byte)(unsafe.Pointer(amp;str))

fmt.Println(byteArr)

個人強(qiáng)烈不推薦這種寫法,因?yàn)榇藭r我們對byteArr的修改將導(dǎo)致超出預(yù)期的行為.

且因?yàn)閟tringStruct的數(shù)據(jù)結(jié)構(gòu)中只有unsafe.Pointer和一個in

溫馨提示

  • 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)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論