字符串相加
本节内容主要代码位于 runtime/string.go
以下面的代码进行说明
1 | a := "Hello " |
当执行 a+b
的时候,底层上调用的是 concatstrings
,并且将a, b 两个操作数作为 slice
传递进去,如下:
tmpBuf
是一个预定义的 tmpStringBufSize
大小的 byte
数组。
1 | const tmpStringBufSize = 32 |
函数中首先定义了三个变量,idx
表示当前遍历的字符串索引号,l
表示当前拼接得到的字符串长度,count
表示长度不为0的字符串个数。
1 | idx := 0 |
然后我们遍历每一个字符串,通过 len
函数获取到它的长度,如果长度为0,执行 continue
,不需要进行其他操作,由于拼接之后的字符串长度可能过长,导致整数溢出,所以源码中使用 l+n<l
加以判断,当溢出的时候抛出错误。执行这个遍历之后,我们获取到了最终的字符串长度。
1 | for i, x := range a { |
遍历完成之后,如果 count
为0,说明并没有实在意义的字符串,直接返回空字符串,如果 count
为1,那么我们直接返回对应的字符串就好,无需进行拼接,这个时候,count
为1,那么必然满足条件的这个字符串的索引号为 idx
,所以直接返回 a[idx]
。
1 | if count == 0 { |
不过这里除了需要判断 count==1
之外,我们还需要判断 a[idx]
是否还在栈上,或者 buf
是否从调用帧逃逸出去了,这样我们才能够直接返回。
这里的 stringDataOnStack
其实还挺好玩的,代码如下:
1 | func stringDataOnStack(s string) bool { |
我们知道其实一个字符串就是一个 stringStruct
结构体,str 指向底层保存的数据,len 保存长度,在运行的每一个 goroutine
都分配了自己的栈空间,我们只需要判断这个地址是否在栈范围之内就好,也就是 stk.lo <= ptr && ptr < stk.hi
。
1 | type stringStruct struct { |
如果上面的条件都不满足,那么我们需要进行下一步操作
1 | s, b := rawstringtmp(buf, l) |
首先调用 rawstringtmp
返回一个 string
类型的 s
和一个 []byte
的 b
,其中 s
的底层数据就是 b
,修改 b
,那么 s
就会被同步修改。循环中其实在不断的将需要拼接的单个字符串摆放在对应的位置
实际上,根据不同的字符串拼接个数,定义了不同的函数,但是本质上都是调用上述函数
1 | func concatstring2(buf *tmpBuf, a [2]string) string { |
调试的时候发现,如果我们的拼接字符串个数在 [2,5]
范围内,那么我们会调用上述函数,然后再调用 concatstrings
,如果超过这个值呢,会直接调用 concatstrings
进行处理。
生活杂笔,学习杂记,偶尔随便写写东西。