Go | 字符串拼接方式总结和分析

1. 拼接方式

  • +=
  • append(,)
  • buf.WriteString()
  • fmt.Sprintf(,)
  • copy(,)

示例代码如下:

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))
}

2. 效率分析

完整目录结构:

在这里插入图片描述

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

3. 源码简单分析

3.1 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)

3.2 buf.Writestring

// 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 中类型为 []bytebuf 是否有足够的空间容纳新增的 s, 如果空间不足(即 !ok)就调用 b.grow 扩充 buf 的空间。

变量 m 表示 b.buf 中现有内容所占的长度(len)。

最后,调用 copy 函数将 s 拷贝到 b.buf 的后半部分,即 b.buf[m:] 中。

需要注意:WriteString 函数的方法注释有说当 buffer 变得很大时,可能会抛出 ErrTooLarge 错误。所以,该方法需要慎用。

补充:一个切片有类型、大小(len)、容量(cap)三个基本要素。容量表示其最大能存储多少数据,大小表示已经存储的数量。

3.3 append

// 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 是一个内嵌函数,我们看不到其底层实现。

但根据注释可知,该函数会返回一个新的切片,所以,我们必须用变量接收该返回值。而且,第二个入参可以是字符串解构。

3.4 copy

// 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) 中小的那个。

4 总结

前面我们已经知道,执行效率从高到低依次是:append > copy>+= > buf.Writestring > fmt.Sprintf

所以,我们应该优先使用 appendcopy 、或 +=buf.Writestring 不但效率低,而且可能还会出错。所以,尽量不要用。


在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CnPeng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值