前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go | 字符串拼接的总结和分析

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

作者头像
CnPeng
发布2021-03-04 14:31:56
4730
发布2021-03-04 14:31:56
举报
文章被收录于专栏:CnPengDevCnPengDev

欢迎关注我的公众号:CnPeng ,工作日 8:08 准时更新。

1. 拼接方式

拼接方式有如下五种:

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

示例代码如下:

代码语言:javascript
复制
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 测试代码,如下:

代码语言:javascript
复制
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

代码语言:javascript
复制
// 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

代码语言:javascript
复制
// 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)三个基本要素。容量表示其最大能存储多少数据,大小表示已经存储的数量。

3.3 append

代码语言:javascript
复制
// 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

代码语言:javascript
复制
// 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。

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


关注我,解锁更多精彩内容

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-02-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 CnPeng 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 拼接方式
  • 2. 效率分析
  • 3. 源码简单分析
    • 3.1 fmt.Sprintf
      • 3.2 buf.Writestring
        • 3.3 append
          • 3.4 copy
          • 4 总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档