简单谈谈Golang中的字符串与字节数组

2020-01-28 13:55:17王振洲

Go 语言中拼接字符串会使用 + 符号,当我们使用这个符号对字符串进行拼接时,编译器会在类型检查阶段将 OADD 节点转换成 OADDSTR 类型的节点,随后在 SSA 中间代码生成的阶段调用 addstr 函数:


func walkexpr(n *Node, init *Nodes) *Node {
 switch n.Op {
 // ...
 case OADDSTR:
 n = addstr(n, init)
 }
}

addstr 函数就是帮助我们在编译期间选择合适的函数对字符串进行拼接,如果需要拼接的字符串小于或者等于 5 个,那么就会直接调用 concatstring{2,3,4,5} 等一系列函数,如果超过 5 个就会直接选择 concatstrings 传入一个数组切片。


func addstr(n *Node, init *Nodes) *Node {
 c := n.List.Len()

 buf := nodnil()
 args := []*Node{buf}
 for _, n2 := range n.List.Slice() {
 args = append(args, conv(n2, types.Types[TSTRING]))
 }

 var fn string
 if c <= 5 {
 fn = fmt.Sprintf("concatstring%d", c)
 } else {
 fn = "concatstrings"

 t := types.NewSlice(types.Types[TSTRING])
 slice := nod(OCOMPLIT, nil, typenod(t))
 slice.List.Set(args[1:])
 args = []*Node{buf, slice}
 }

 cat := syslook(fn)
 r := nod(OCALL, cat, nil)
 r.List.Set(args)
 // ...

 return r
}

其实无论使用 concatstring{2,3,4,5} 中的哪一个,最终都会调用 concatstrings ,在这个函数中我们会先对传入的切片参数进行遍历,首先会过滤空字符串并获取拼接后字符串的长度。


func concatstrings(buf *tmpBuf, a []string) string {
 idx := 0
 l := 0
 count := 0
 for i, x := range a {
 n := len(x)
 if n == 0 {
 continue
 }
 if l+n < l {
 throw("string concatenation too long")
 }
 l += n
 count++
 idx = i
 }
 if count == 0 {
 return ""
 }

 if count == 1 && (buf != nil || !stringDataOnStack(a[idx])) {
 return a[idx]
 }
 s, b := rawstringtmp(buf, l)
 for _, x := range a {
 copy(b, x)
 b = b[len(x):]
 }
 return s
}

如果非空字符串的数量为 1 并且当前的字符串不在栈上或者没有逃逸出调用堆栈,那么就可以直接返回该字符串,不需要进行任何的耗时操作。

但是在正常情况下,原始的多个字符串都会被调用 copy 将所有的字符串拷贝到目标字符串所在的内存空间中,新的字符串其实就是一片新的内存空间,与原来的字符串没有任何关联。