欢迎关注我的公众号:CnPeng ,工作日 8:08 准时更新。
拼接方式有如下五种:
+=
示例代码如下:
package str
import (
"bytes"
"fmt"
"time"
)
func Add(s1, s2 string) string {
s1 += s2
return s1
}
func FmtSprintf(s1, s2 string) string {
s1 = fmt.Sprintf("%s%s", s1, s2)
return s1
}
func AppendStr(s1, s2 string) string {
byteSlice := append([]byte(s1), s2...)
return string(byteSlice)
}
func BufWriteStr(s1, s2 string) string {
buf := bytes.Buffer{}
buf.WriteString(s1)
buf.WriteString(s2)
return buf.String()
}
func CopyStr(s1, s2 string) string {
resultSlice := make([]byte, len(s1)+len(s2))
copy(resultSlice[:len(s1)], s1)
copy(resultSlice[len(s1):], s2)
return string(resultSlice)
}
func StrAppendTest(s1, s2 string) {
fmt.Println("Add-", Add(s1, s2))
fmt.Println("FmtSprintf-", FmtSprintf(s1, s2))
fmt.Println("Append-", AppendStr(s1, s2))
fmt.Println("BufWriter-", BufWriteStr(s1, s2))
fmt.Println("Copy-", CopyStr(s1, s2))
}
完整目录结构:
在 test 包中新建 str_append_test.go 文件,并在其中编写 Benchmark 测试代码,如下:
package test
import (
"../str"
"testing"
)
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
str.Add("123", "ABC")
}
}
func BenchmarkSprintf(b *testing.B) {
for i := 0; i < b.N; i++ {
str.FmtSprintf("123", "ABC")
}
}
func BenchmarkAppend(b *testing.B) {
for i := 0; i < b.N; i++ {
str.AppendStr("123", "ABC")
}
}
func BenchmarkBufWrite(b *testing.B) {
for i := 0; i < b.N; i++ {
str.BufWriteStr("123", "ABC")
}
}
func BenchmarkCopyStr(b *testing.B) {
for i := 0; i < b.N; i++ {
str.CopyStr("123", "ABC")
}
}
然后在终端中通过 cd 命令切换到 str_append_test.go 文件所在目录,并执行:go test -bench=. -run=none。得到运行结果如下图:
通过上图我们可以看出,执行效率从高到低依次是:append > copy > += > buf.Writestring > fmt.Sprintf
// Sprintf formats according to a format specifier and returns the resulting string.
func Sprintf(format string, a ...interface{}) string {
p := newPrinter()
p.doPrintf(format, a)
s := string(p.buf)
p.free()
return s
}
该函数先构建了一个 newPrinter() 对象,然后调用了其 doPrintf(format, a) 函数,该函数内部是对 format 和 a 的解析和转换,函数内部最终调用 append() 拼接得到一个字节切片,并将该切片赋值给 p.buf,所以在上面的代码中就有了 s := string(p.buf)
// WriteString appends the contents of s to the buffer, growing the buffer as
// needed. The return value n is the length of s; err is always nil. If the
// buffer becomes too large, WriteString will panic with ErrTooLarge.
func (b *Buffer) WriteString(s string) (n int, err error) {
b.lastRead = opInvalid
m, ok := b.tryGrowByReslice(len(s))
if !ok {
m = b.grow(len(s))
}
return copy(b.buf[m:], s), nil
}
上面的代码中,先调用了 b.tryGrowByReslice(len(s)) 判断 b 中类型为 []byte 的 buf 是否有足够的空间容纳新增的 s, 如果空间不足(即 !ok)就调用 b.grow 扩充 buf 的空间。
变量 m 表示 b.buf 中现有内容所占的长度(len)。
最后,调用 copy 函数将 s 拷贝到 b.buf 的后半部分,即 b.buf[m:] 中。
需要注意:WriteString 函数的方法注释有说当 buffer 变得很大时,可能会抛出 ErrTooLarge 错误。所以,该方法需要慎用。
补充:一个切片有类型、大小(len)、容量(cap)三个基本要素。容量表示其最大能存储多少数据,大小表示已经存储的数量。
// The append built-in function appends elements to the end of a slice. If
// it has sufficient capacity, the destination is resliced to accommodate the
// new elements. If it does not, a new underlying array will be allocated.
// Append returns the updated slice. It is therefore necessary to store the
// result of append, often in the variable holding the slice itself:
// slice = append(slice, elem1, elem2)
// slice = append(slice, anotherSlice...)
// As a special case, it is legal to append a string to a byte slice, like this:
// slice = append([]byte("hello "), "world"...)
func append(slice []Type, elems ...Type) []Type
append 是一个内嵌函数,我们看不到其底层实现。
但根据注释可知,该函数会返回一个新的切片,所以,我们必须用变量接收该返回值。而且,第二个入参可以是字符串解构。
// The copy built-in function copies elements from a source slice into a
// destination slice. (As a special case, it also will copy bytes from a
// string to a slice of bytes.) The source and destination may overlap. Copy
// returns the number of elements copied, which will be the minimum of
// len(src) and len(dst).
func copy(dst, src []Type) int
copy 也是一个内嵌函数。
返回值是一个 int, 表示 src 中有多少数据被拷贝到 dst 中了,取值为 len(src) 和 len(dst) 中小的那个。
前面我们已经知道,执行效率从高到低依次是:append、copy、+= 、 buf.Writestring 、fmt.Sprintf。
所以,我们应该优先使用 append、copy 、或 +=。buf.Writestring 不但效率低,而且可能还会出错。所以,尽量不要用。
关注我,解锁更多精彩内容