首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Go语言中常见100问题-#41 substrings and memory leaks

Go语言中常见100问题-#41 substrings and memory leaks

作者头像
数据小冰
发布于 2023-08-17 00:34:15
发布于 2023-08-17 00:34:15
21600
代码可运行
举报
文章被收录于专栏:数据小冰数据小冰
运行总次数:0
代码可运行
子串和内存泄露

Go语言中常见100问题-#26 slices and memory leaks讨论切片可能导致的内存泄露问题,其实对于字符串也可能会导致内存泄露。下面来分析在操作字符串的时候如何防止内存泄露。

获取一个字符串的子串可以采用下面的语句。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
s1 := "Hello, World!"
s2 := s1[:5] // Hello

s2截取了s1的前5个字符,注意这里s1中的字符都是简单字符,所以直接基于s1的前5个byte创建一个字符串,如果子串的字符存在非简单字符的情况,所谓的非简单字符是指该字符(rune)编码后由多个字节构成。如果s1中含有非简单字符,截取前5个字符需要采用下面的编码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
s1 := "Hêllo, World!"
s2 := string([]rune(s1)[:5]) // Hêllo

现在我们对字符串的子串操作有了新的认识,下面通过例子说明可能导致内存泄露问题。handleLog完成这样的功能:接收一个字符串类型的入参,表示log日志信息,每条log有两部分构成:uuid+日志内容,uuid具有唯一标识性,可以理解为每条log的uuid都是不同的,我们想在内存中存储每条日志的uuid信息,例如保留最近的n个uuid信息,需要注意,日志的内容可能很长,高达几千个字节。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (s store) handleLog(log string) error {
 if len(log) < 36 {
  return errors.New("log is not correctly formatted")
 }
 uuid := log[:36]
 s.store(uuid)
 // Do something
}

通过截取子串的方式获取log的uuid(log[:36]),然后将uuid信息传递给s.store存储起来。看起来代码没有问题,实际上是存在问题的。

在Go语言中,对字符串子串操作,没有规范字符串和原串是否共享相同的数据,即是否复用底层的back array. 但是标准的Go编译器处理方法是让它们共享相同的back array, 这样做兼顾了内存和性能两方面,即减少内存开销又提高了性能。由于log可能很长,log[:36]创建的子串引用了log底层的数组,因此整个log占用的内存都不会释放,导致内存泄露问题。

如何修复呢?采用深度拷贝,截取的uuid不再共享log底层数组,而是新创建back array.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (s store) handleLog(log string) error {
 if len(log) < 36 {
  return errors.New("log is not correctly formatted")
 }
 uuid := string([]byte(log[:36]))
 s.store(uuid)
 // Do something
}

通过将log[:36]转换为[]byte,然后在将[]byte转成string,防止内存泄露问题,此时得到的uuid字符串底层是一个含有36个字节的数组,而不是与log共享底层数组。

注意,一些编辑器或linters会提示string([]byte(s))是不必要的操作,例如,像Goland会提示 redundant type conversion. 在大部分场景下这样处理确实是冗余操作,但是在本文场景中这样处理确实是有必要的,我们需要识别有时候IDE给出的告警提示是不准确的。

「NOTE字符串是指针类型,将字符串传给函数时,并不是深拷贝,函数内操作的字符串和外部共享同一个底层back array.」

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func prints(s string) {
 sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
 fmt.Printf("data:%v,len:%d\n", sh.Data, sh.Len)
}

func main() {
 s := "hello world"
 sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
 fmt.Printf("data:%v,len:%d\n", sh.Data, sh.Len)
 prints(s)
}

上述程序可以验证传递给prints的字符串变量s和main函数中的s执行的地址(data)是相同的,在我的电脑上运行输出结果如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
data:17468238,len:11
data:17468238,len:11

Go1.18版本开始,标准库提供strings.Clone方法,它返回一个新的字符串。本文问题采用strings.Clone实现关键语句如下,将log[:36]内容拷贝到一个新分配的内存空间中,防止内存泄露。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
uuid := strings.Clone(log[:36])

总结:对于字符串的子串操作,我们需要留意两个事情。一是字符串内部是基于byte实现的,而不是rune。二是,不恰当的子串操作会导致内存泄露问题,因为截取的子串和原串仍然在共享底层内存,处理方法是采用深拷贝,或者使用strings.Clone(Go1.18版本开始提供)。

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

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

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Go错误集锦 | 字符串底层原理及常见错误
string是Go语言的基础类型,在实际项目中针对字符串的各种操作使用频率也较高。本文就介绍一下在使用string时容易犯的一些错误以及如何避免。
Go学堂
2023/01/31
4030
go 字符串
要修改字符串,需要先将其转换成[]rune或[]byte,完成后再转换为string。无论哪种转换,都会重新分配内存,并复制字节数组。
Michel_Rolle
2021/02/04
3.1K0
golang string和[]byte的对比
为啥string和[]byte类型转换需要一定的代价? 为啥内置函数copy会有一种特殊情况copy(dst []byte, src string) int? string和[]byte,底层都是数组
sunsky
2020/08/19
4.4K0
【Go】string 优化误区及建议
初学 Go 语言的朋友总会在传 []byte 和 string 之间有着很多纠结,实际上是没有了解 string 与 slice 的本质,而且读了一些程序源码,也发现很多与之相关的问题,下面类似的代码估计很多初学者都写过,也充分说明了作者当时内心的纠结:
thinkeridea
2019/11/04
1K0
【Go】string 优化误区及建议
Go语言中常见100问题-#37 Inaccurate string iteration
对字符串进行迭代是一个非常常见的操作,例如我们想要对字符串中的每个rune做一些操作或者实现一个查找具体子串的函数。但是字符串迭代中存在一些误区,需要了解掌握,避免踩坑。
数据小冰
2023/08/17
1810
Go语言中常见100问题-#37 Inaccurate string iteration
Golang升级到1.7后,之前正确的函数出现错误,分析原因及解决办法
最近尝试把开发环境,升级到Golang1.7.1后,程序会偶发性的宕掉,查看日志后,发现总是在一个计算切片的哈希值的地方,错误信息是:
henrylee2cn
2019/04/04
1.6K0
Go通关18:SliceHeader,slice 如何高效处理数据?
示例中变量 a1 的类型是 [1]string,变量 a2 的类型是 [2]string,因为它们大小不一致,所以不是同一类型。
微客鸟窝
2021/08/18
6531
Go语言中常用的基本数据类型
Go语言中以bool类型进行声明布尔型数据,布尔型数据只有true(真)和false(假)两个值。
极客运维圈
2020/03/23
1.3K0
Go基础——字符串
字符串结构由两个信息组成:第一个是字符串指向的底层字节数组,第二个是字符串的字节的长度。字符串其实是一个结构体,因此字符串的赋值操作也就是reflect.StringHeader结构体的复制过程,并不会涉及底层字节数组的复制。在前面数组一节提到的[2]string字符串数组对应的底层结构和[2]reflect.StringHeader对应的底层结构是一样的,可以将字符串数组看作一个结构体数组。 我们可以看看字符串“Hello, world”本身对应的内存结构:
羊羽shine
2019/05/28
5630
Go语言中常见100问题-#36 Not understanding the concept of a rune
通过字符可以简化字符集定义,但是在Unicode中,使用代码点来标识字符,字符集中的每个字符都有唯一的代码点值。例如,中文汉字的代码点值是U+6C49. 如果采用UTF-8编码,汉字存储占3个字节:0xE6,0xB1,0x89. 理解这些非常重要,因为在Go语言中,1个rune字符是一个代码点。
数据小冰
2023/08/17
1890
Go语言中常见100问题-#36 Not understanding the concept of a rune
[]byte与string的两种转换方式和底层实现
对小许公众号点了关注的朋友,应该都看过小许之前的文章《fasthttp是如何做到比net/http快十倍的》,相信你们还对极致的优化方式意犹未尽。
小许code
2024/03/25
4681
[]byte与string的两种转换方式和底层实现
我为什么放弃Go语言?
👉腾小云导读 你在什么时候会产生“想要放弃用 Go 语言”的念头?也许是在用 Go 开发过程中,接连不断踩坑的时候。本文作者提炼和总结《100 Go Mistakes and How to Avoid Them》里的精华内容,并结合自身的工作经验,盘点了 Go 的常见典型错误,撰写了这篇超全避坑指南。让我们跟随文章,一起重拾用 Go 的信心~ 👉目录 1 注意 shadow 变量 2 慎用 init 函数 3 embed types 优缺点 4 Functional Options Pattern 传递参数
腾讯云开发者
2023/06/06
1.9K0
我为什么放弃Go语言?
[Go] GO中的字符串底层数据结构
一个字符串是一个不可改变的字节序列,字符串通常是用来包含人类可读的文本数据。和数组不同的是,字符串的元素不可修改,是一个只读的字节数组。每个字符串的长度虽然也是固定的,但是字符串的长度并不是字符串类型的一部分。由于Go语言的源代码要求是UTF8编码,导致Go源代码中出现的字符串面值常量一般也是UTF8编码的。源代码中的文本字符串通常被解释为采用UTF8编码的Unicode码点(rune)序列。
唯一Chat
2020/12/28
1.1K0
[Go] GO中的字符串底层数据结构
聊一聊字符串内部化
字符串作为一种不可变值类型,在多数的语言里,其底层基本都是个只读的字节数组:一旦被创建,则不可被改写。正是因为其只读特性,如果有大量相同的字符串需要处理,那么在内存中就会保存多份,显然是非常浪费内存的。
poslua
2019/08/21
5870
聊一聊字符串内部化
Go字符串 【Go语言圣经笔记】
一个字符串是一个不可改变的字节序列(笔者注:修改一个字符串会产生新的字符串)。字符串可以包含任意的数据,包括byte值0,但是通常是用来包含人类可读的文本。文本字符串通常被解释为采用UTF8编码的Unicode码点(rune)序列,我们稍后会详细讨论这个问题。
Steve Wang
2021/12/06
4650
100 个 Go 错误以及如何避免:5~8
我们将在本章中看到 Go 有一个非常独特的处理字符串的方法。Go 引入了一个概念叫做符文;这个概念对于理解是必不可少的,可能会让新手感到困惑。一旦我们知道了字符串是如何被管理的,我们就可以避免在字符串上迭代时的常见错误。我们还将看看 Go 开发者在使用或生成字符串时所犯的常见错误。此外,我们会看到有时我们可以直接使用[]byte工作,避免额外的分配。最后,我们将讨论如何避免一个常见的错误,这个错误会造成子字符串的泄漏。本章的主要目的是通过介绍常见的字符串错误来帮助你理解字符串在 Go 中是如何工作的。
ApacheCN_飞龙
2023/10/13
1.1K0
100 个 Go 错误以及如何避免:5~8
区块链开发之Go语言—字符串和字节
字符串与字节的关系 Go 代码使用 UTF-8 编码,字符串和字节之间的转换依据的是UTF-8编码。注意中文是3个字节对应一个中文的字符串。 下面将归类讲述负责操作字符串和字节的几个标准库 strings 包提供了很多操作字符串的简单函数,通常一般的字符串操作需求都可以在这个包中找到。 bytes 包提供了对应操作字节的函数。 strconv 包提供了基本数据类型和字符串之间的转换。这个包之所以存在,是因为在Go中,没有隐式类型转换。字符串类型和 int、float、bool 等类型之间的转换却没有这么简单
linxinzhe
2018/04/10
1.4K0
Go语言基础之基本数据类型
整型分为以下两个大类: 按长度分为:int8、int16、int32、int64 对应的无符号整型:uint8、uint16、uint32、uint64 其中,uint8就是我们熟知的byte型,int16对应C语言中的short型,int64对应C语言中的long型。
twelvecoder
2021/12/24
5770
Golang-optimization「2」: 字符串
本系列的第二个部分,本文来谈谈程序员们喜闻乐见的string在Golang中有哪些值得注意的优化点
Kevinello
2022/11/17
3850
Golang-optimization「2」: 字符串
Go语言中常见100问题-#39 Under-optimized string concatenation
在Go语言中,字符串连接主要有两种方法,其中一种在某些时候是非常低效的,通过本文学习我们应该掌握在不同的场景下选择最合适的方法。
数据小冰
2023/08/17
1700
Go语言中常见100问题-#39 Under-optimized string concatenation
相关推荐
Go错误集锦 | 字符串底层原理及常见错误
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档