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都是可以的。

相关推荐

  1. golang字符串拼接

    2024-01-12 11:00:04       59 阅读
  2. golang字符串拼接和strings.Builder

    2024-01-12 11:00:04       33 阅读
  3. C#字符串拼接

    2024-01-12 11:00:04       31 阅读
  4. PostgreSQL拼接字符串方法

    2024-01-12 11:00:04       58 阅读
  5. go拼接字符串方法

    2024-01-12 11:00:04       35 阅读
  6. 字符串拼接+和+=执行过程

    2024-01-12 11:00:04       51 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-01-12 11:00:04       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-01-12 11:00:04       106 阅读
  3. 在Django里面运行非项目文件

    2024-01-12 11:00:04       87 阅读
  4. Python语言-面向对象

    2024-01-12 11:00:04       96 阅读

热门阅读

  1. 数据库系统原理总结之——数据库编程

    2024-01-12 11:00:04       57 阅读
  2. 近日遇到数据库及其他问题

    2024-01-12 11:00:04       60 阅读
  3. neo4j中如何并列执行多条命令

    2024-01-12 11:00:04       55 阅读
  4. GBASE南大通用 访问其他数据库服务器

    2024-01-12 11:00:04       41 阅读
  5. go 语言常见问题(3)

    2024-01-12 11:00:04       68 阅读
  6. 50天精通Golang(第14天)

    2024-01-12 11:00:04       53 阅读
  7. Unity敌人的自动巡逻脚本

    2024-01-12 11:00:04       58 阅读
  8. 服务器带宽有什么用? 带宽不足怎么办?

    2024-01-12 11:00:04       51 阅读
  9. Jtti:新手用户如何构建HTML 5 Web页面?

    2024-01-12 11:00:04       51 阅读
  10. SpringMVC传值与取值

    2024-01-12 11:00:04       53 阅读