Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Go语言中常见100问题-#39 Under-optimized string concatenation

Go语言中常见100问题-#39 Under-optimized string concatenation

作者头像
数据小冰
发布于 2023-08-17 00:33:37
发布于 2023-08-17 00:33:37
16800
代码可运行
举报
文章被收录于专栏:数据小冰数据小冰
运行总次数:0
代码可运行
字符串连接优化

在Go语言中,字符串连接主要有两种方法,其中一种在某些时候是非常低效的,通过本文学习我们应该掌握在不同的场景下选择最合适的方法。

下面的concat函数通过+=将一个字符串切片拼接成一个字符串。具体代码如下,在每轮循环中,通过+=操作符将切片中的字符串value拼接到字符串s中。咋看起来这段代码没有啥问题,但是我们不要忽略了一个重要原则:字符串是不可变的。因此每一轮迭代,不是直接更新s,而是在内存中重新分配一个字符串,这会很影响性能。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func concat(values []string) string {
 s := ""
 for _, value := range values {
  s += value
 }
 return s
}

有什么优化方法吗?可以采用strings包提供的Builder结构体,实现如下。通过strings.Builder创建一个Builder结构体,在每次迭代中调用它的WriteString方法向里面的缓冲区buffer中追加value,减少内存的重新分配和拷贝。尽管WriteString方法第二返回值是一个error类型,但我们通常忽略它,因为该方法返回的error永远都是nil. 那为啥函数签名设计这样,搞一个error返回值呢?因为strings.Builder实现了io.StringWriter接口,该接口只有一个方法: WriteString(s string) (n int, err error),因此为实现该接口,所以strings.Builder的WriteString方法会返回一个error。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func concat(values []string) string {
 sb := strings.Builder{}
 for _, value := range values {
  _, _ = sb.WriteString(value)
 }
 return sb.String()
}

strings.Builder除了提供有WriteString方法,还有Write方法,写入一个字节切片,如果是写入单个字节,使用WriteByte方法,如果是写入一个rune字符,使用WriteRune方法。

内部实现上,strings.Builder含有一个字节切片,调用WriteString实际上是向内部的切片中append数据。有两点需要注意,一是strings.Builder不支持并发,内部同时调用append操作会导致数据竞争问题。二是需要设置内部切片的大小,否则如切片满了会重新分配空间,并拷贝原切片中的数据,导致效率低效。所以strings.Builder提供了一个对外方法Grow(n int)确保内部分配的空间有n个字节。

现在使用Grow方法对上面的代码进一步优化,一开始就设置好写入的所有字符串的字节数。实现代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func concat(values []string) string {
 total := 0
 for i := 0; i < len(values); i++ {
  total += len(values[i])
 }

 sb := strings.Builder{}
 sb.Grow(total)
 for _, value := range values {
  _, _ = sb.WriteString(value)
 }
 return sb.String()
}

对上面三种字符串拼接实现做一个benckmark测试,输入的字符串切片包含1000个字符串,每个字符串长度为1000字节。测试结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
BenchmarkConcatV1-4             16      72291485 ns/op
BenchmarkConcatV2-4           1188        878962 ns/op
BenchmarkConcatV3-4           5922        190340 ns/op

可以看到,第3个版本比第1个版本快99%,比第2个版本快78%。也许有人会问,第三个版本不是对values进行两次迭代,但为啥它的效率比第二个版本还高呢?答案是没有预初始化切片大小,导致效率地下,在本系列文章Go语言中常见100问题-#21 Inefficient slice initialization有详细分析。如果切片没有分配给定的容量,当切片不断append元素变满时,会导致额外的内存分配和数据拷贝。因此,采用两次迭代先统计占用的空间大小是值得的。

采用strings.Builder来拼接字符串是推荐的方法,这种方法建议在有循环的时候使用。如果没有循环,只是简单的个别字符串拼接,不推荐这种方法,因为性能提升并不明显,但使得代码的可读性比第一个版本差,直接使用+=或者fmt.Sprintf拼接即可。

一般来说,从性能角度触发,我们需要记住如果拼接的字符串数量超过5个,采用strings.Builder进行拼接通常是比+=要快的。即使准确的数量(不一定是5)依赖于很多因素,像待拼接的字符串长度、运行的机器等。但这可以作为一个经验值在我们选择方法时提供一个参考。此外,如果我们预先知道拼接的字符串长度,应该使用Grow预分配足量的内存空间。

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

本文分享自 数据小冰 微信公众号,前往查看

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
记一次提升18倍的性能优化
最近负责的一个自研的 Dubbo 注册中心经常收到 CPU 使用率的告警,于是进行了一波优化,效果还不错,于是打算分享下思考、优化过程,希望对大家有一些帮助。
龟仙老人
2021/11/21
4150
记一次提升18倍的性能优化
Go语言字符串高效拼接(二)
在上一篇关于字符串拼接的文章 Go语言字符串高效拼接(一) 中,我们演示的多种字符串拼接的方式,并且使用一个例子来测试了他们的性能,通过对比发现,我们觉得性能高的Builder并未发挥出其应该的性能,反而+号拼接,甚至strings.Join方法的性能更优越,那么这到底是什么原因呢?今天我们开始解开他们神秘的面纱,解开谜底。
飞雪无情
2018/12/06
6550
Go语言核心36讲(Go语言实战与应用十五)--学习笔记
Go 语言不但拥有可以独立代表 Unicode 字符的类型rune,而且还有可以对字符串值进行 Unicode 字符拆分的for语句。
郑子铭
2021/11/27
3560
Go语言核心36讲(Go语言实战与应用十五)--学习笔记
Golang 语言怎么高效使用字符串?
在 Golang 语言中,string 类型的值是只读的,不可以被修改。如果需要修改,通常的做法是对原字符串进行截取和拼接操作,从而生成一个新字符串,但是会涉及内存分配和数据拷贝,从而有性能开销。本文我们介绍在 Golang 语言中怎么高效使用字符串。
frank.
2021/04/16
2K0
go语言string之Buffer与Builder
操作字符串离不开字符串的拼接,但是Go中string是只读类型,大量字符串的拼接会造成性能问题。
Tim在路上
2021/02/04
6.1K0
Go语言中常见100问题-#96 Not knowing how to reduce allocations
减少内存分配是Go应用程序的一个常见优化事项。本系列文章已介绍了不少减少堆上内存分配的方法:
数据小冰
2024/02/01
1710
Go语言中常见100问题-#96 Not knowing how to reduce allocations
Go语言如何高效的进行字符串拼接(6种方式进行对比分析)
string是一个8位字节的集合,通常但不一定代表UTF-8编码的文本。string可以为空,但是不能为nil。string的值是不能改变的。
Golang梦工厂
2022/07/11
7580
Go语言如何高效的进行字符串拼接(6种方式进行对比分析)
Go语言中拼接字符串的技巧
当你要拼接一个字符串切片时,可以使用strings.Join函数。这是一种高效的方式。
运维开发王义杰
2023/08/21
4940
Go语言中拼接字符串的技巧
Go错误集锦 | 字符串底层原理及常见错误
string是Go语言的基础类型,在实际项目中针对字符串的各种操作使用频率也较高。本文就介绍一下在使用string时容易犯的一些错误以及如何避免。
Go学堂
2023/01/31
3990
Go语言中常见100问题-#37 Inaccurate string iteration
对字符串进行迭代是一个非常常见的操作,例如我们想要对字符串中的每个rune做一些操作或者实现一个查找具体子串的函数。但是字符串迭代中存在一些误区,需要了解掌握,避免踩坑。
数据小冰
2023/08/17
1810
Go语言中常见100问题-#37 Inaccurate string iteration
Go语言字符串高效拼接(一)
在我们变成的时候,和字符串打交道是必不可少的,我们对数据库里文本的处理,Web文本的显示,文本数据的存储等都需要和字符串打交道,那么对于字符串来说,查找、拼接这些都是常用的操作,尤其是以拼接使用的比较多,比如把一个人的姓名和年龄拼接在一起显示。
飞雪无情
2018/12/04
2.2K0
Go语言核心36讲(Go语言实战与应用十七)--学习笔记
在上一篇文章中,我们分享了bytes.Buffer中已读计数的大致功用,并围绕着这个问题做了解析,下面我们来进行相关的知识扩展。
郑子铭
2021/11/29
3330
Go语言核心36讲(Go语言实战与应用十七)--学习笔记
Go语言中常见100问题-#41 substrings and memory leaks
在Go语言中常见100问题-#26 slices and memory leaks讨论切片可能导致的内存泄露问题,其实对于字符串也可能会导致内存泄露。下面来分析在操作字符串的时候如何防止内存泄露。
数据小冰
2023/08/17
2150
Go语言中常见100问题-#41 substrings and memory leaks
一步步提升Go语言生成随机字符串的效率
假如我们要生成一个固定长度的随机字符串,包含大小写字母,没有数字,没有特殊字符串,那么我们怎么做呢?需要怎样优化,才会更简单,更高效?在最终的方案之前,我们看看最常见的写法是怎样的,然后是如何一步步演进到最终的高效率方案的。好吧,先看下最原始的方案。
飞雪无情
2020/02/10
1.9K1
Go 语言中的字符串基本操作
这篇文章已经放到腾讯智能工作台的知识库啦,链接在这里:ima.copilot-Go 入门到入土。要是你有啥不懂的地方,就去知识库找 AI 聊一聊吧。
叫我阿杰好了
2025/06/16
1310
Go 语言中的字符串基本操作
go 字符串
要修改字符串,需要先将其转换成[]rune或[]byte,完成后再转换为string。无论哪种转换,都会重新分配内存,并复制字节数组。
Michel_Rolle
2021/02/04
3.1K0
Go语言核心36讲(Go语言实战与应用十六)--学习笔记
strings包和bytes包可以说是一对孪生兄弟,它们在 API 方面非常的相似。单从它们提供的函数的数量和功能上讲,差别可以说是微乎其微。
郑子铭
2021/11/28
3030
Go语言核心36讲(Go语言实战与应用十六)--学习笔记
100 个 Go 错误以及如何避免:5~8
我们将在本章中看到 Go 有一个非常独特的处理字符串的方法。Go 引入了一个概念叫做符文;这个概念对于理解是必不可少的,可能会让新手感到困惑。一旦我们知道了字符串是如何被管理的,我们就可以避免在字符串上迭代时的常见错误。我们还将看看 Go 开发者在使用或生成字符串时所犯的常见错误。此外,我们会看到有时我们可以直接使用[]byte工作,避免额外的分配。最后,我们将讨论如何避免一个常见的错误,这个错误会造成子字符串的泄漏。本章的主要目的是通过介绍常见的字符串错误来帮助你理解字符串在 Go 中是如何工作的。
ApacheCN_飞龙
2023/10/13
1K0
100 个 Go 错误以及如何避免:5~8
Golang 生成随机字符串的高级玩法!
这个世界很简单,复杂的是人。总有那么一波人要搞个大新闻,他们玩的就是人群中的不一样!于是乎,就有了下面这位老哥的高赞回答。
七点一刻
2022/06/14
3.4K0
Go语言字符串高效拼接(三)
在上一篇关于字符串拼接的文章Go语言字符串高效拼接(二) 中,我们终于为Builder拼接正名了,果真不负众望,尤其是拼接的字符串越来越多时,其性能的优越性更加明显。
飞雪无情
2018/12/12
1.1K0
相关推荐
记一次提升18倍的性能优化
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验