golang字符串本質與原理詳解_第1頁
golang字符串本質與原理詳解_第2頁
golang字符串本質與原理詳解_第3頁
golang字符串本質與原理詳解_第4頁
golang字符串本質與原理詳解_第5頁
已閱讀5頁,還剩7頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

第golang字符串本質與原理詳解目錄一、字符串的本質1.字符串的定義2.字符串的長度3.字符與符文二、字符串的原理1.字符串的解析2.字符串的拼接3.字符串的轉換總結

一、字符串的本質

1.字符串的定義

golang中的字符(character)串指的是所有8比特位字節(jié)字符串的集合,通常(非必須)是UTF-8編碼的文本。字符串可以為空,但不能是nil。字符串在編譯時即確定了長度,值是不可變的。

//go/src/builtin/builtin.go

//stringisthesetofallstringsof8-bitbytes,conventionallybutnot

//necessarilyrepresentingUTF-8-encodedtext.Astringmaybeempty,but

//notnil.Valuesofstringtypeareimmutable.

typestringstring

字符串在本質上是一串字符數組,每個字符在存儲時都對應了一個或多個整數,整數是多少取決于字符集的編碼方式。

s:="golang"

fori:=0;ilen(s);i++{

fmt.Printf("s[%v]:%v\n",i,s[i])

//s[0]:103

//s[1]:111

//s[2]:108

//s[3]:97

//s[4]:110

//s[5]:103

字符串在編譯時類型為string,在運行時其類型定義為一個結構體,位于reflect包中:

//go/src/reflect/value.go

//StringHeaderistheruntimerepresentationofastring.

//...

typeStringHeaderstruct{

Datauintptr

Lenint

}

根據運行時字符串的定義可知,在程序運行的過程中,字符串存儲了長度(Len)及指向實際數據的指針(Data)。

2.字符串的長度

golang中所有文件都采用utf8編碼,字符常量也使用utf8編碼字符集。1個英文字母占1個字節(jié)長度,一個中文占3個字節(jié)長度。go中對字符串取長度len(s)指的是字節(jié)長度,而不是字符個數,這與動態(tài)語言如python中的表現有所差別。如:

print(len("go語言"))

#4

s:="go語言"

fmt.Printf("len(s):%v\n",len(s))

//len(s):8

3.字符與符文

go中存在一個特殊類型符文類型(rune),用來表示和區(qū)分字符串中的字符。rune的本質是int32。字符串符文的個數往往才比較符合我們直觀感受上的字符串長度。要計算字符串符文長度,可以先將字符串轉為[]rune類型,或者利用標準庫中的utf8.RuneCountInString()函數。

s:="go語言"

fmt.Println(len([]rune(s)))

count:=utf8.RuneCountInString(s)

fmt.Println(count)

//4

當用range遍歷字符串時,遍歷的就不再是單字節(jié),而是單個符文rune。

s:="go語言"

for_,r:=ranges{

fmt.Printf("rune:%vstring:%#U\n",r,r)

//rune:103unicode:U+0067'g'

//rune:111unicode:U+006F'o'

//rune:35821unicode:U+8BED'語'

//rune:35328unicode:U+8A00'言'

二、字符串的原理

1.字符串的解析

golang在詞法解析階段,通過掃描源代碼,將雙引號和反引號開頭的內容分別識別為標準字符串和原始字符串:

//go/src/cmd/compile/internal/syntax/scanner.go

func(s*scanner)next(){

switchs.ch{

case'"':

s.stdString()

case'`':

s.rawString()

...

然后,不斷的掃描下一個字符,直到遇到另一個雙引號和反引號即結束掃描。并通過string(s.segment())將解析到的字節(jié)轉換為字符串,同時通過setLlit()方法將掃描到的內容類型(kind)標記為StringLit。

func(s*scanner)stdString(){

ok:=true

s.nextch()

for{

ifs.ch=='"'{

s.nextch()

break

s.nextch()

s.setLit(StringLit,ok)

func(s*scanner)rawString(){

ok:=true

s.nextch()

for{

ifs.ch=='`'{

s.nextch()

break

s.nextch()

s.setLit(StringLit,ok)

//setLitsetsthescannerstateforarecognized_Literaltoken.

func(s*scanner)setLit(kindLitKind,okbool){

s.nlsemi=true

s.tok=_Literal

s.lit=string(s.segment())

s.bad=!ok

s.kind=kind

}

2.字符串的拼接

字符串可以通過+進行拼接:

s:="go"+"lang"

在編譯階段構建抽象語法樹時,等號右邊的go+lang會被解析為一個字符串相加的表達式(AddStringExpr)節(jié)點,該表達式的操作op為OADDSTR。相加的各部分字符串被解析為節(jié)點Node列表,并賦給表達式的List字段:

//go/src/cmd/compile/internal/ir/expr.go

//AnAddStringExprisastringconcatenationExpr[0]+Exprs[1]+...+Expr[len(Expr)-1].

typeAddStringExprstruct{

miniExpr

ListNodes

Prealloc*Name

funcNewAddStringExpr(possrc.XPos,list[]Node)*AddStringExpr{

n:=AddStringExpr{}

n.pos=pos

n.op=OADDSTR

n.List=list

returnn

}

在構建抽象語法樹時,會遍歷整個語法樹的表達式,在遍歷的過程中,識別到操作Op的類型為OADDSTR,則會調用walkAddString對字符串加法表達式進行進一步處理:

//go/src/cmd/compile/internal/walk/expr.go

funcwalkExpr(nir.Node,init*ir.Nodes)ir.Node{

n=walkExpr1(n,init)

returnn

funcwalkExpr1(nir.Node,init*ir.Nodes)ir.Node{

switchn.Op(){

caseir.OADDSTR:

returnwalkAddString(n.(*ir.AddStringExpr),init)

}

walkAddString首先計算相加的字符串的個數c,如果相加的字符串個數小于2,則會報錯。接下來會對相加的字符串字節(jié)長度求和,如果字符串總字節(jié)長度小于32,則會通過stackBufAddr()在??臻g開辟一塊32字節(jié)的緩存空間。否則會在堆區(qū)開辟一個足夠大的內存空間,用于存儲多個字符串。

//go/src/cmd/compile/internal/walk/walk.go

consttmpstringbufsize=32

//go/src/cmd/compile/internal/walk/expr.go

funcwalkAddString(n*ir.AddStringExpr,init*ir.Nodes)ir.Node{

c:=len(n.List)

ifc2{

base.Fatalf("walkAddStringcount%dtoosmall",c)

buf:=typecheck.NodNil()

ifn.Esc()==ir.EscNone{

sz:=int64(0)

for_,n1:=rangen.List{

ifn1.Op()==ir.OLITERAL{

sz+=int64(len(ir.StringVal(n1)))

//Don'tallocatethebufferiftheresultwon'tfit.

ifsztmpstringbufsize{

//Createtemporarybufferforresultstringonstack.

buf=stackBufAddr(tmpstringbufsize,types.Types[types.TUINT8])

//buildlistofstringarguments

args:=[]ir.Node{buf}

for_,n2:=rangen.List{

args=append(args,typecheck.Conv(n2,types.Types[types.TSTRING]))

varfnstring

ifc=5{

//smallnumbersofstringsusedirectruntimehelpers.

//note:order.exprknowsthiscutofftoo.

fn=fmt.Sprintf("concatstring%d",c)

}else{

//largenumbersofstringsarepassedtotheruntimeasaslice.

fn="concatstrings"

t:=types.NewSlice(types.Types[types.TSTRING])

//args[1:]toskipbufarg

slice:=ir.NewCompLitExpr(base.Pos,ir.OCOMPLIT,t,args[1:])

slice.Prealloc=n.Prealloc

args=[]ir.Node{buf,slice}

slice.SetEsc(ir.EscNone)

cat:=typecheck.LookupRuntime(fn)

r:=ir.NewCallExpr(base.Pos,ir.OCALL,cat,nil)

r.Args=args

r1:=typecheck.Expr(r)

r1=walkExpr(r1,init)

r1.SetType(n.Type())

returnr1

}

如果用于相加的字符串個數小于等于5個,則會調用運行時的字符串拼接concatstring1-concatstring5函數。否則調用運行時的concatstrings函數,并將字符串通過切片slice的形式傳入。類型檢查中的typecheck.LookupRuntime(fn)方法查找到運行時的字符串拼接函數后,將其構建為一個調用表達式,操作Op為OCALL,最后遍歷調用表達式完成調用。concatstring1-concatstring5中的每一個調用最終都會調用concatstrings函數。

//go/src/runtime/string.go

consttmpStringBufSize=32

typetmpBuf[tmpStringBufSize]byte

funcconcatstring2(buf*tmpBuf,a0,a1string)string{

returnconcatstrings(buf,[]string{a0,a1})

funcconcatstring3(buf*tmpBuf,a0,a1,a2string)string{

returnconcatstrings(buf,[]string{a0,a1,a2})

funcconcatstring4(buf*tmpBuf,a0,a1,a2,a3string)string{

returnconcatstrings(buf,[]string{a0,a1,a2,a3})

funcconcatstring5(buf*tmpBuf,a0,a1,a2,a3,a4string)string{

returnconcatstrings(buf,[]string{a0,a1,a2,a3,a4})

}

concatstring1-concatstring5已經存在一個32字節(jié)的臨時緩存空間供其使用,并通過slicebytetostringtmp函數將該緩存空間的首地址作為字符串的地址,字節(jié)長度作為字符串的長度。如果待拼接字符串的長度大于32字節(jié),則會調用rawstring函數,該函數會在堆區(qū)為字符串分配存儲空間,并且將該存儲空間的地址指向字符串。由此可以看出,字符串的底層是字節(jié)切片,且指向同一片內存區(qū)域。在分配好存儲空間、完成指針指向等工作后,待拼接的字符串切片會被一個一個地通過內存拷貝copy(b,x)到分配好的存儲空間b上。

//concatstringsimplementsaGostringconcatenationx+y+z+...

funcconcatstrings(buf*tmpBuf,a[]string)string{

l:=0

fori,x:=rangea{

n:=len(x)

l+=n

s,b:=rawstringtmp(buf,l)

for_,x:=rangea{

copy(b,x)

b=b[len(x):]

returns

funcrawstringtmp(buf*tmpBuf,lint)(sstring,b[]byte){

ifbuf!=nill=len(buf){

b=buf[:l]

s=slicebytetostringtmp(b[0],len(b))

}else{

s,b=rawstring(l)

return

funcslicebytetostringtmp(ptr*byte,nint)(strstring){

stringStructOf(str).str=unsafe.Pointer(ptr)

stringStructOf(str).len=n

return

//rawstringallocatesstorageforanewstring.Thereturned

//stringandbyteslicebothrefertothesamestorage.

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

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

stringStructOf(s).str=p

stringStructOf(s).len=size

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

return

typestringStructstruct{

strunsafe.Pointer

lenint

funcstringStructOf(sp*string)*stringStruct{

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

}

3.字符串的轉換

盡管字符串的底層是字節(jié)數組,但字節(jié)數組與字符串的相互轉換并不是簡單的指針引用,而是涉及了內存復制。當字符串大于32字節(jié)時,還需要申請堆內存。

s:="go語言"

b:=[]byte(s)//stringtoslicebyte

ss:=string(b)//slicebytetostring

當字符串轉換為字節(jié)切片時,需要調用stringtoslicebyte函數,當字符串小于32字節(jié)時,可以直接使用緩存buf,但是當字節(jié)長度大于等于32時,rawbyteslice函數需要向堆區(qū)申請足夠的內存空間,然后通過內存復制將字符串拷貝到目標地址。

//go/src/runtime/string.go

funcstringtoslicebyte(buf*tmpBuf,sstring)[]byte{

varb[]byte

ifbuf!=nillen(s)=len(buf){

*buf=tmpBuf{}

b=buf[:len(s)]

}else{

b=rawbyteslice(len(s))

copy(b,s)

returnb

funcrawbyteslice(sizeint)(b[]byte){

cap:=roundupsize(uintptr(size))

p:=mallocgc(c

溫馨提示

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

評論

0/150

提交評論