前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go字符串拼接 ,那种性能最佳?

Go字符串拼接 ,那种性能最佳?

作者头像
Mandy的名字被占用了
发布2023-02-28 10:01:20
2.5K0
发布2023-02-28 10:01:20
举报
文章被收录于专栏:菜鸟成长学习笔记

字符串是每一门编程语言必不可缺的数据类型,作为强大的Go语言也一样。在日常的开发工作中,对Go字符串的操作是必不可少的,但不同的操作方式,其性能也是不同的。本文将从五大操作方式来进行演示,到底哪一种操作方式最佳。

相关阅读

为什么说Go字符串不能修改

使用 + 运算符连接字符串

在 Go 中连接字符串的最简单方法是使用连接运算符("+")。

代码语言:javascript
复制
func main() {
 name := "John"
 email := "john.doe@qq.com"

 s := name + "'s" + " email address is " + email

 fmt.Println(s)
}

运行结果如下:

代码语言:javascript
复制
John's email address is john.doe@qq.com

对于如上所示的基本字符串连接操作,这应该足够了。但是,对于较大的操作,例如在循环中连接几个大字符串时,它可能效率低下。这是因为,字符串是不可变的数据结构,每个连接操作都会在内存中创建一个全新的字符串。关于为什么会重新创建一个全新的字符串,可以参考,为什么说Go中的字符串类型是不可修改的

使用fmt.Sprint()函数,拼接字符串

在Go中,fmt.Sprint()函数用于格式化数据,因此也可以用来拼接字符串。

代码语言:javascript
复制
func main() {
 s1 := "John"
 s2 := 20

 s3 := fmt.Sprintf("[name]: %s; [age]: %d", s1, s2)

 fmt.Println(s3)
}

运行结果如下:

代码语言:javascript
复制
[name]: John; [age]: 20

使用strings.join()函数

在Go官网的strings包中,也提供join()函数对字符串切片进行拼接,因此该函数也可以用来拼接字符串。不过函数的第一个参数必须是一个切片。

代码语言:javascript
复制
func main() {
 s := []string
  "a",
  "quick",
  "brown",
  "fox",
  "jumps",
  "over",
  "the",
  "lazy",
  "dog",
 }

 fmt.Println(strings.Join(s, " ")) 

 fmt.Println(strings.Join(s, "_")) 
}

最终运行的结果为:

代码语言:javascript
复制
a quick brown fox jumps over the lazy dog
a_quick_brown_fox_jumps_over_the_lazy_dog

使用bytes.Buffer

该方式是在 Go v1.10 版本被引入,也是一种高效拼接字符串的首选方案。其底层是提供一个缓冲区,实现内容的操作。它的零值是一个随时可用的空字节缓冲区,它允许通过以下方法操作缓冲区:

代码语言:javascript
复制
func (b *Buffer) Write(p []byte) (int, error) 
func (b *Buffer) WriteByte(c byte) error
func (b *Buffer) WriteRune(r rune) (int, error)
func (b *Buffer) WriteString(s string) (int, error)

为了更好的演示其性能效果,不采用上面直接定义字符串,而是采用读文件再拼接内容的方式。接下来,我们先创建一个文件 hello.txt ,并写入如下内容:

代码语言:javascript
复制
a
quick
brown
fox
jumps
over
the
lazy
dog

创建读取文件内容的代码:

代码语言:javascript
复制
func main() {

 var buf bytes.Buffer

 file, err := os.Open("hello.txt")
 if err != nil {
  log.Fatal(err)
 }

 scanner := bufio.NewScanner(file)
 for scanner.Scan() {
  text := strings.TrimSpace(scanner.Text())

  text = strings.ToUpper(text) + " "


  _, err := buf.WriteString(text)

  if err != nil {
   log.Fatal(err)
  }
 }

 if err := scanner.Err(); err != nil {
  log.Fatal(err)
 }

 fmt.Println(strings.TrimSpace(buf.String()))

}

最终运行结果如下:

代码语言:javascript
复制
A QUICK BROWN FOX JUMPS OVER THE LAZY DOG

同时该包也提供两个方法,来控制缓冲区的大小。

1、Grow():用来预分配内存空间大小,如果实现知道所需内存大小,使用该函数来申请空间大小,这样减少后期的缓冲区大小操作,效率更高。

2、 Reset():用于清空缓冲区。

使用strings.Builder

除了使用上面的bytes.Buffer,Go官方也提供了strings.Builder包来操作字符串。我们采用和上面一样的操作方式,来拼接字符串。

代码语言:javascript
复制
func main() {

 var builder strings.Builder


 file, err := os.Open("hello.txt")
 if err != nil {
  log.Fatal(err)
 }

 scanner := bufio.NewScanner(file)
 for scanner.Scan() {
  text := strings.TrimSpace(scanner.Text())

  text = strings.ToUpper(text) + " "

  _, err := builder.WriteString(text)

  if err != nil {
   log.Fatal(err)
  }
 }

 if err := scanner.Err(); err != nil {
  log.Fatal(err)
 }

 fmt.Println(strings.TrimSpace(builder.String()))

}

最终输出的结果如下:

代码语言:javascript
复制
A QUICK BROWN FOX JUMPS OVER THE LAZY DOG

基准测试

上面总结了,几种操作字符串的方法,那究竟哪种方案是最佳呢?下面将写一组基准测试来实现压测对比。

代码语言:javascript
复制
package main

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

var loremIpsum = `
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas non odio eget quam gravida laoreet vitae id est. Cras sit amet porta dui. Pellentesque at pulvinar ante. Pellentesque leo dolor, tristique a diam vel, posuere rhoncus ex. Mauris gravida, orci eu molestie pharetra, mi nibh bibendum arcu, in bibendum augue neque ac nulla. Phasellus consectetur turpis et neque tincidunt molestie. Vestibulum diam quam, sodales quis nulla eget, volutpat euismod mauris.
`

var strSLice = make([]string, LIMIT)

const LIMIT = 1000

func init() {
 for i := 0; i < LIMIT; i++ {
  strSLice[i] = loremIpsum
 }
}

func BenchmarkConcatenationOperator(b *testing.B) {
 for i := 0; i < b.N; i++ {
  var q string
  for _, v := range strSLice {
   q = q + v
  }
 }
 b.ReportAllocs()
}

func BenchmarkFmtSprint(b *testing.B) {
 for i := 0; i < b.N; i++ {
  var q string
  for _, v := range strSLice {
   q = fmt.Sprint(q, v)
  }
 }
 b.ReportAllocs()
}

func BenchmarkStringsJoin(b *testing.B) {
 for i := 0; i < b.N; i++ {
  q := strings.Join(strSLice, "")

  _ = q
 }
 b.ReportAllocs()
}

func BenchmarkBytesBuffer(b *testing.B) {
 for i := 0; i < b.N; i++ {
  var q bytes.Buffer

  q.Grow(len(loremIpsum) * len(strSLice))

  for _, v := range strSLice {
   q.WriteString(v)
  }
  _ = q.String()
 }
 b.ReportAllocs()
}

func BenchmarkStringBuilder(b *testing.B) {
 for i := 0; i < b.N; i++ {
  var q strings.Builder

  q.Grow(len(loremIpsum) * len(strSLice))

  for _, v := range strSLice {
   q.WriteString(v)
  }
  _ = q.String()
 }
 b.ReportAllocs()
}

使用如下命令,查看运行结果:

代码语言:javascript
复制
go test -bench=.

最终输出结果:

代码语言:javascript
复制
goos: linux
goarch: amd64
pkg: github.com/Freshman-tech/golang
cpu: Intel(R) Core(TM) i7-10875H CPU @ 2.30GHz
BenchmarkConcatenationOperator-16             51          24837485 ns/op        238062937 B/op      1009 allocs/op
BenchmarkFmtSprint-16                         21          53225816 ns/op        488510658 B/op      5146 allocs/op
BenchmarkStringsJoin-16                    27043             43857 ns/op          475137 B/op          1 allocs/op
BenchmarkBytesBuffer-16                    15705             76785 ns/op          950274 B/op          2 allocs/op
BenchmarkStringBuilder-16                  29877             40188 ns/op          475136 B/op          1 allocs/op
PASS
ok      github.com/Freshman-tech/golang 7.687s

通过上图,不难看出,使用strings.Builder的性能效果更佳。

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

本文分享自 菜鸟成长学习笔记 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 相关阅读
  • 使用 + 运算符连接字符串
  • 使用fmt.Sprint()函数,拼接字符串
  • 使用strings.join()函数
  • 使用bytes.Buffer
  • 使用strings.Builder
  • 基准测试
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档