golang字符串本质与原理详解_第1页
golang字符串本质与原理详解_第2页
golang字符串本质与原理详解_第3页
golang字符串本质与原理详解_第4页
golang字符串本质与原理详解_第5页
已阅读5页,还剩7页未读, 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第golang字符串本质与原理详解目录一、字符串的本质1.字符串的定义2.字符串的长度3.字符与符文二、字符串的原理1.字符串的解析2.字符串的拼接3.字符串的转换总结

一、字符串的本质

1.字符串的定义

golang中的字符(character)串指的是所有8比特位字节字符串的集合,通常(非必须)是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个字节长度,一个中文占3个字节长度。go中对字符串取长度len(s)指的是字节长度,而不是字符个数,这与动态语言如python中的表现有所差别。如:

print(len("go语言"))

#4

s:="go语言"

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

//len(s):8

3.字符与符文

go中存在一个特殊类型符文类型(rune),用来表示和区分字符串中的字符。rune的本质是int32。字符串符文的个数往往才比较符合我们直观感受上的字符串长度。要计算字符串符文长度,可以先将字符串转为[]rune类型,或者利用标准库中的utf8.RuneCountInString()函数。

s:="go语言"

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

count:=utf8.RuneCountInString(s)

fmt.Println(count)

//4

当用range遍历字符串时,遍历的就不再是单字节,而是单个符文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())将解析到的字节转换为字符串,同时通过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)节点,该表达式的操作op为OADDSTR。相加的各部分字符串被解析为节点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,则会报错。接下来会对相加的字符串字节长度求和,如果字符串总字节长度小于32,则会通过stackBufAddr()在栈空间开辟一块32字节的缓存空间。否则会在堆区开辟一个足够大的内存空间,用于存储多个字符串。

//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字节的临时缓存空间供其使用,并通过slicebytetostringtmp函数将该缓存空间的首地址作为字符串的地址,字节长度作为字符串的长度。如果待拼接字符串的长度大于32字节,则会调用rawstring函数,该函数会在堆区为字符串分配存储空间,并且将该存储空间的地址指向字符串。由此可以看出,字符串的底层是字节切片,且指向同一片内存区域。在分配好存储空间、完成指针指向等工作后,待拼接的字符串切片会被一个一个地通过内存拷贝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.字符串的转换

尽管字符串的底层是字节数组,但字节数组与字符串的相互转换并不是简单的指针引用,而是涉及了内存复制。当字符串大于32字节时,还需要申请堆内存。

s:="go语言"

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

ss:=string(b)//slicebytetostring

当字符串转换为字节切片时,需要调用stringtoslicebyte函数,当字符串小于32字节时,可以直接使用缓存buf,但是当字节长度大于等于32时,rawbyteslice函数需要向堆区申请足够的内存空间,然后通过内存复制将字符串拷贝到目标地址。

//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. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论