只会用+拼接字符串吗?你该重学一下Go了!

在go语言中,字符串是一种不可变的字节序列。字符串拼接是指将两个或多个字符串合并成一个新的字符串的过程。go语言提供了多种字符串拼接的方式,本文将介绍其中的几种,并进行基准测试比较。

一、直接使用+进行拼接

这是最简单和直观的方法,只需要使用+运算符将两个或多个字符串相加即可。例如:

s1 := "Hello"
s2 := "World"
s3 := s1 + " " + s2 // s3 = "Hello World"

这种方法的优点是易于理解和使用,不需要引入额外的包或函数。但是,这种方法的缺点是效率低下,因为每次拼接都会创建一个新的字符串,分配新的内存空间,并复制原来的字符串内容。如果需要拼接很多字符串,或者在循环中进行拼接,这种方法会造成大量的内存开销和性能损失。

二、strings.Builder

这是一种高效和推荐的方法,它使用了strings包中的Builder类型。Builder是一个可变的字节缓冲区,可以用来构建字符串。它提供了WriteString方法,可以将一个字符串追加到Builder中。最后,可以使用String方法将Builder转换为一个不可变的字符串。例如:

var b strings.Builder
b.WriteString("Hello")
b.WriteString(" ")
b.WriteString("World")
s := b.String() // s = "Hello World"

这种方法的优点是效率高,因为它避免了不必要的内存分配和复制。它也比较简洁和易用,只需要调用两个方法即可。但是,这种方法的缺点是需要引入strings包,并且不能直接使用+运算符进行拼接。

三、bytes.Buffer

这是一种类似于strings.Builder的方法,它使用了bytes包中的Buffer类型。Buffer也是一个可变的字节缓冲区,可以用来构建字符串。它提供了WriteString方法,可以将一个字符串追加到Buffer中。最后,可以使用String方法将Buffer转换为一个不可变的字符串。例如:

var b bytes.Buffer
b.WriteString("Hello")
b.WriteString(" ")
b.WriteString("World")
s := b.String() // s = "Hello World"

这种方法和strings.Builder非常相似,优缺点也基本一致。不过,bytes.Buffer还提供了其他一些方法,比如WriteByte、WriteRune、WriteTo等,可以更灵活地操作字节。

四、fmt.Sprintf

这是一种格式化和惯用的方法,它使用了fmt包中的Sprintf函数。Sprintf函数可以根据一个模板和一些参数生成一个格式化的字符串,并返回该字符串。例如:

s1 := "Hello"
s2 := "World"
s3 := fmt.Sprintf("%s %s", s1, s2) // s3 = "Hello World"

这种方法的优点是清晰和易读,可以根据需要生成各种格式的字符串,并且可以处理不同类型的参数,比如int、bool等,而不需要显式转换。但是,这种方法的缺点是效率低下,因为它涉及到反射、格式解析、内存分配等操作。而且,这种方法不适合在不知道参数数量和类型的情况下进行拼接。

五、append []byte

这是一种直接操作字节切片的方法,它使用了内置的append函数。append函数可以将一个或多个元素追加到一个切片中,并返回该切片。如果要拼接字符串,可以先将字符串转换为字节切片,然后使用append函数进行拼接,最后再将字节切片转换为字符串。例如:

s1 := "Hello"
s2 := "World"
b := []byte(s1)
b = append(b, ' ')
b = append(b, s2...)
s3 := string(b) // s3 = "Hello World"

这种方法的优点是效率高,因为它直接操作字节,避免了额外的内存分配和复制。但是,这种方法的缺点是不够简洁和易用,需要进行多次类型转换,并且不能直接使用+运算符进行拼接。

六、各种方式的基准测试比较

为了比较各种方式的性能,我们可以使用testing包中的Benchmark函数进行基准测试。下面是一个简单的测试代码,用来比较拼接10个字符串的时间和内存消耗:

package main

import (
	"bytes"
	"fmt"
	"strings"
	"testing"
)

var strs = []string{"This", "is", "a", "benchmark", "test", "for", "string", "concatenation", "in", "Go"}

func BenchmarkPlus(b *testing.B) {
	for i := 0; i < b.N; i++ {
		var s string
		for _, str := range strs {
			s += str
		}
	}
}

func BenchmarkBuilder(b *testing.B) {
	for i := 0; i < b.N; i++ {
		var b strings.Builder
		for _, str := range strs {
			b.WriteString(str)
		}
		b.String()
	}
}

func BenchmarkBuffer(b *testing.B) {
	for i := 0; i < b.N; i++ {
		var b bytes.Buffer
		for _, str := range strs {
			b.WriteString(str)
		}
		b.String()
	}
}

func BenchmarkSprintf(b *testing.B) {
	for i := 0; i < b.N; i++ {
		fmt.Sprintf("%s%s%s%s%s%s%s%s%s%s", strs[0], strs[1], strs[2], strs[3], strs[4], strs[5], strs[6], strs[7], strs[8], strs[9])
	}
}

func BenchmarkAppend(b *testing.B) {
	for i := 0; i < b.N; i++ {
		var b []byte
		for _, str := range strs {
			b = append(b, str...)
		}
		string(b)
	}
}

运行该测试代码,可以得到以下结果:

goos: darwin
goarch: arm64
pkg: test2/str
BenchmarkPlus-8          5765235               203.1 ns/op           256 B/op          9 allocs/op
BenchmarkBuilder-8      11304490               105.5 ns/op           120 B/op          4 allocs/op
BenchmarkBuffer-8       15438054                74.89 ns/op          112 B/op          2 allocs/op
BenchmarkSprintf-8       3949538               302.5 ns/op           208 B/op         11 allocs/op
BenchmarkAppend-8       10571956               115.2 ns/op           168 B/op          5 allocs/op
PASS
ok      test2/str       6.866s

从结果可以看出:

  1. Sprintf是性能最差的,直接使用+进行拼接性能也很差;
  2. bytes.Buffer内存开辟次数、大小、耗时、每秒运行次数都是最好的
  3. strings.Builder和Append性能也还不错,一般情况下也不会形成差距

当然,这只是一个简单的测试,不同的情况结果可能会有所不同,取决于字符串的数量、长度、内容等因素。所以,在选择字符串拼接的方法时,还需要根据具体的场景和需求进行权衡和测试。实际应用中,使用strings.Builder等已经能满足我们的实际需求。在某些特殊情况下,在追求极致性能的时候,我们还可以通过与分配内存、底层数据转换等方式获取更好的性能,后面有机会再进行详细的讲述。

#go##golang项目##goalng##go项目##golang研发实习生#
全部评论

相关推荐

ArisRobert:统一解释一下,第4点的意思是,公司按需通知员工,没被通知到的员工是没法去上班的,所以只要没被通知到,就自动离职。就是一种比较抽象的裁员。
点赞 评论 收藏
分享
吃不饱的肱二头肌很想退休:tnnd 我以为选妹子呢,亏我兴高采烈的冲进来😠
投递快手等公司10个岗位
点赞 评论 收藏
分享
2 1 评论
分享
牛客网
牛客企业服务