golang中的字符串拼接

go中常见的字符串拼接方法

假设我们现在要实现这样一个拼接函数: 将字符串重复n次拼接起来,返回一个新字符串。

方法一:使用+运算符

func simpleSplice(s string, n int) string {
	newStr := ""
	for i := 0; i < n; i++ {
		newStr += s
	}
	return newStr
}

方法二:使用Sprintf

func sprintfSplice(s string, n int) string {
	newStr := ""
	for i := 0; i < n; i++ {
		newStr = fmt.Sprintf("%s%s", newStr, s)
	}
	return newStr
}

方法三:使用[]byte

func bytesSplice(s string, n int) string {
	newStr := []byte{}
	for i := 0; i < n; i++ {
		newStr = append(newStr, []byte(s)...)
	}
	return string(newStr)
}

方法四:使用bytes.Buffer

func bufferSplice(s string, n int) string {
	buffer := bytes.Buffer{}
	for i := 0; i < n; i++ {
		buffer.WriteString(s)
	}
	return buffer.String()
}

方法五:使用strings.Builder

func builderSplice(s string, n int) string {
	builder := strings.Builder{}
	for i := 0; i < n; i++ {
		builder.WriteString(s)
	}
	return builder.String()
}

性能测试

我们对上面五种方法进行benchmark测试。

测试函数类似这样,生成100长度的随机字符串,然后拼接100次。

func BenchmarkBytesSplice3(b *testing.B) {
	for i := 0; i < b.N; i++ {
		bytesSplice3(genStr(100), 100)
	}
}

结果如下:

BenchmarkSimpleSplice
BenchmarkSimpleSplice-12     	    9901	    123436 ns/op
BenchmarkSprintfSplice
BenchmarkSprintfSplice-12    	    8151	    144824 ns/op
BenchmarkBytesSplice
BenchmarkBytesSplice-12      	   62271	     19435 ns/op
BenchmarkBuilderSplice
BenchmarkBuilderSplice-12    	   93918	     11890 ns/op
BenchmarkBufferSplice
BenchmarkBufferSplice-12     	   97413	     11816 ns/op

以上仅测试了一次, 不要求严谨, 只是为了说明问题

可以看见使用+运算符拼接和Sprintf的性能是最差的。

+运算符/Sprintf

+运算符和Sprintf性能差的原因其实差不多: 每次都会创建一个新的字符串,然后将原来的字符串和新的字符串拼接起来,这样就会产生很多的临时字符串,这些临时字符串会占用很多的内存,而且还会增加GC的负担。

bytes/strings.Builder/buffer

这三者都是利用[]byte实现的功能,所以从当前这个测试中来看差别不大(对比上面那两个来说~)。

我们查看buffer的源码发现, 他每次写入之前会计算好所需的空间, 然后将其copy[]byte中。

Builder中虽然使用append追加的数据, 但是使用了unsafe方法直接操作内存指针。

所以操作[]byte来实现拼接是最快, 而bufferBuilder带来的内存优化有多少呢?

内存占用

我们调大需要测试的字符串长度, 这样会更明显: 字符串长度1000, 拼接1000次。

func BenchmarkBytesSplice3(b *testing.B) {
	for i := 0; i < b.N; i++ {
		bytesSplice3(genStr(1000), 1000)
	}
}

运行结果

BenchmarkBytesSplice3-12            2421            521164 ns/op         2033674 B/op       1003 allocs/op
BenchmarkBuilderSplice-12           1135            986065 ns/op         5238292 B/op         26 allocs/op
BenchmarkBufferSplice-12            2733            451784 ns/op         3105806 B/op         14 allocs/op

BytesSplice3为什么会有1003 allocs ? 明明已经预分配内存了

func bytesSplice3(s string, n int) string {
    // 预分配内存   
	newStr := make([]byte, 0, len(s)*n)
	for i := 0; i < n; i++ {
        // 问题在这里, 每次都会创建一个新的[]byte
        // 其实这个动作可以省略掉
		newStr = append(newStr, []byte(s)...)
        // 更换成这种写法试试
        // newStr = append(newStr, s...)
	}
	return *(*string)(unsafe.Pointer(&newStr))
}

更换后:

BenchmarkBytesSplice3-12            8913            135231 ns/op         1009667 B/op          3 allocs/op
BenchmarkBuilderSplice-12           1530            756342 ns/op         5238292 B/op         26 allocs/op
BenchmarkBufferSplice-12            3198            381564 ns/op         3105808 B/op         14 allocs/op

总结

在可预知拼接结果长度的情况下, 使用make([]byte, 0, len(s)*n)这样的方式来预分配内存是最合适的。 其他情况下, 使用strings.Builder, buffer都是可以的。

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值