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

2023-02-28 10:01:20 浏览数 (1)

字符串是每一门编程语言必不可缺的数据类型,作为强大的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的性能效果更佳。

0 人点赞