第六届字节跳动青训营第五课(go的string) | 青训营

24 阅读3分钟

看完这篇我终于知道 …

能GET到的知识点

  • go的优化
  • go里的字符串的实现原理
  • 高效的拼接字符串

来自于GPT的回答

我:Go为什么避免字符串拼接!!

GPT:

在Go语言中,字符串是不可变的。这意味着每次对字符串进行拼接操作,都会创建一个新的字符串对象。由于字符串不可变,为了执行拼接操作,必须分配一块新的内存,并且将原始字符串和要拼接的字符串复制到新的内存中,这会引入额外的开销和性能消耗。

为了避免这种性能问题,Go语言提供了strings.Builder类型,它允许高效地构建字符串。strings.Builder使用内部缓冲区来处理字符串拼接,避免了每次拼接都创建新字符串的开销。你可以通过调用strings.BuilderWriteString方法将多个字符串追加到同一个缓冲区中,然后通过调用String方法将缓冲区内容转换为一个字符串。

以下是使用strings.Builder的示例代码:

go
复制代码
package main

import (
	"fmt"
	"strings"
)

func main() {
	builder := strings.Builder{}
	builder.WriteString("Hello")
	builder.WriteString(", ")
	builder.WriteString("world!")

	result := builder.String()
	fmt.Println(result) // 输出:Hello, world!
}

使用strings.Builder可以避免创建多个中间字符串对象,从而提高代码的性能和效率。

总结上述回答:可以看出上述回答也说明了字符串是不可变的,如需要对字符串进行拼接的话会进行拷贝原来字符串创建一个新的内存来进行拼接。也不代表gpt的描述完全正确,我们只是友好🧑‍🤝‍🧑借鉴一下,

进入正题

在go语言中,字符串是不可变的,在许多语言也如此例如py等,拼接字符串实际上是创建了一个新的字符串对象。如果代码存在大量的拼接操作且也是大的字符串,消耗的性能极其难受。

用数据说话

五个测试案例

go
复制代码
package stringstest  
  
import (  
"bytes"  
"fmt"  
"math/rand"  
"strings"  
)  
  
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"  

// 通过+操作符
func ConcatByPlus(n int, str string) string {  
    s := ""  
    for i := 0; i < n; i++ {  
        s += str  
    }  
    return s  
}  
// 通过fmt包的Sprintf进行拼接
func ConcatSprintf(n int, str string) string {  
    s := ""  
    for i := 0; i < n; i++ {  
        s = fmt.Sprintf("%s%s", s, str)  
    }  
    return s  
}  
  
 // 通过提供的strings.Builder
func ConcatSBuilder(n int, str string) string {  
    var builder strings.Builder  
    for i := 0; i < n; i++ {  
        builder.WriteString(str)  
    }  
    return builder.String()  
}  

 // 通过缓冲池
func ConcatBBuffer(n int, str string) string {  
    s := ""  
    buf := new(bytes.Buffer)  
    for i := 0; i < n; i++ {  
        buf.WriteString(s)  
    }  
    return buf.String()  
}  

// 通过容量未0的byte切片
func ConcatBytes(n int, str string) string {  
    buf := make([]byte, 0)  
    for i := 0; i < n; i++ {  
        buf = append(buf, str...)  
    }  
    return string(buf)  
}  

// 随机生成字符串
func RandStr(n int) string {  
     
    b := make([]byte, n)  
    for i := range b {  
        b[i] = letterBytes[rand.Intn(len(letterBytes))]  
    }  
    return string(b)  
}

image.png

从基准测试的结果可以看出,+和Sprintf的效率极其的低,个人认为sprintf底层进行对字符串的for循环且需要将格式化符号在进行拼接等操作。可以看出最好的是ConcatBBuffer几乎做到零消耗。在于bytes.Buffer底层维护了一个可变的字节切片,只需要添加到切片,而不会重新分配内存或复制数据。且动态扩容机制。

再说一说go字符串的实现

go的字符串是不可变的,为什么上一个不可变的呢?实际上底层是基于字节数组。string表示结构,该结构包含了,字符串的长度和底层字节数组的指针。每个字符串值在内存中是一块连续的内存空间使其访问更高效,因此在获取字符串长度O(1)时间复杂度,之所以不可变是为了保证字符串安全和高效访问。

总结

在开放阶段可以适当的使用+操作符因为方便嘛!你懂的。但是在需要对大量的字符串拼接时推荐使用strings.Buildbytes.Buffer但是在Buffer里有着怎么一句 To build strings more efficiently, see the strings.Builder type.如果需要构建更高效的字符串操作,推荐使用strings.Builder