字符串相加

本节内容主要代码位于 runtime/string.go

以下面的代码进行说明

1
2
3
a := "Hello "
b := "World "
_ = a + b

当执行 a+b 的时候,底层上调用的是 concatstrings,并且将a, b 两个操作数作为 slice 传递进去,如下:

tmpBuf 是一个预定义的 tmpStringBufSize 大小的 byte 数组。

1
2
const tmpStringBufSize = 32
type tmpBuf [tmpStringBufSize]byte

函数中首先定义了三个变量,idx 表示当前遍历的字符串索引号,l 表示当前拼接得到的字符串长度,count 表示长度不为0的字符串个数。

1
2
3
idx := 0
l := 0
count := 0

然后我们遍历每一个字符串,通过 len 函数获取到它的长度,如果长度为0,执行 continue,不需要进行其他操作,由于拼接之后的字符串长度可能过长,导致整数溢出,所以源码中使用 l+n<l 加以判断,当溢出的时候抛出错误。执行这个遍历之后,我们获取到了最终的字符串长度。

1
2
3
4
5
6
7
8
9
10
11
12
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
}

遍历完成之后,如果 count 为0,说明并没有实在意义的字符串,直接返回空字符串,如果 count 为1,那么我们直接返回对应的字符串就好,无需进行拼接,这个时候,count 为1,那么必然满足条件的这个字符串的索引号为 idx,所以直接返回 a[idx]

1
2
3
4
5
6
7
if count == 0 {
return ""
}

if count == 1 && (buf != nil || !stringDataOnStack(a[idx])) {
return a[idx]
}

不过这里除了需要判断 count==1 之外,我们还需要判断 a[idx] 是否还在栈上,或者 buf 是否从调用帧逃逸出去了,这样我们才能够直接返回。

这里的 stringDataOnStack 其实还挺好玩的,代码如下:

1
2
3
4
5
func stringDataOnStack(s string) bool {
ptr := uintptr(stringStructOf(&s).str)
stk := getg().stack
return stk.lo <= ptr && ptr < stk.hi
}

我们知道其实一个字符串就是一个 stringStruct 结构体,str 指向底层保存的数据,len 保存长度,在运行的每一个 goroutine 都分配了自己的栈空间,我们只需要判断这个地址是否在栈范围之内就好,也就是 stk.lo <= ptr && ptr < stk.hi

1
2
3
4
type stringStruct struct {
str unsafe.Pointer
len int
}

如果上面的条件都不满足,那么我们需要进行下一步操作

1
2
3
4
5
s, b := rawstringtmp(buf, l)
for _, x := range a {
copy(b, x)
b = b[len(x):]
}

首先调用 rawstringtmp 返回一个 string 类型的 s 和一个 []byteb,其中 s 的底层数据就是 b,修改 b,那么 s 就会被同步修改。循环中其实在不断的将需要拼接的单个字符串摆放在对应的位置

实际上,根据不同的字符串拼接个数,定义了不同的函数,但是本质上都是调用上述函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func concatstring2(buf *tmpBuf, a [2]string) string {
return concatstrings(buf, a[:])
}

func concatstring3(buf *tmpBuf, a [3]string) string {
return concatstrings(buf, a[:])
}

func concatstring4(buf *tmpBuf, a [4]string) string {
return concatstrings(buf, a[:])
}

func concatstring5(buf *tmpBuf, a [5]string) string {
return concatstrings(buf, a[:])
}

调试的时候发现,如果我们的拼接字符串个数在 [2,5] 范围内,那么我们会调用上述函数,然后再调用 concatstrings,如果超过这个值呢,会直接调用 concatstrings 进行处理。


生活杂笔,学习杂记,偶尔随便写写东西。

作者

Edgar

发布于

2021-06-26

更新于

2021-12-21

许可协议

评论