网易首页 > 网易号 > 正文 申请入驻

Golang 语言怎么高效使用字符串?

0
分享至

介绍

在 Golang 语言中,string 类型的值是只读的,不可以被修改。如果需要修改,通常的做法是对原字符串进行截取和拼接操作,从而生成一个新字符串,但是会涉及内存分配和数据拷贝,从而有性能开销。本文我们介绍在 Golang 语言中怎么高效使用字符串。

字符串的数据结构

在 Golang 语言中,字符串的值存储在一块连续的内存空间,我们可以把存储数据的内存空间看作一个字节数组,字符串在 runtime 中的数据结构是一个结构体 stringStruct,该结构体包含两个字段,分别是指针类型的 str 和整型的 len。字段 str 是指向字节数组头部的指针值,字段 len 的值是字符串的长度(字节个数)。

我们通过示例代码,比较一下字符串和字符串指针的性能差距。我们定义两个函数,分别用 和 作为函数的参数。

output:

阅读上面这段代码,我们可以发现使用字符串作为参数,和使用字符串指针作为参数,它们的性能基本相同。

虽然字符串的值并不是具体的数据,而是一个指向存储字符串数据的内存地址的指针和一个字符串的长度,但是字符串仍然是值类型。

字符串是只读的,不可修改

在 Golang 语言中,字符串是只读的,它不可以被修改。

阅读上面这段代码,我们将字符串类型的变量 str 转换为字节切片类型,并赋值给变量 byteSlice,使用索引下标修改 byteSlice 的值,打印结果仍未发生改变。

因为字符串转换为字节切片,Golang 编译器会为字节切片类型的变量重新分配内存来存储数据,而不是和字符串类型的变量共用同一块内存空间。

可能会有读者想到用指针修改字符串类型的变量存储在内存中的数据。

阅读上面这段代码,我们可以发现在代码中尝试通过指针修改 string 类型的 str 变量的存储在内存中的数据,结果引发了 signal SIGBUS 运行时错误,从而证明 string 类型的变量是只读的。

我们已经知道字符串在 runtime 中的结构体包含两个字段,指向存储数据的内存地址的指针和字符串的长度,因为字符串是只读的,字符串被赋值后,它的数据和长度都不会被修改,所以读取字符串的长度,实际上就是读取字段 len 的值,复杂度是 O(1)。

在字符串比较时,因为字符串是只读的,不可修改的,所以只要两个比较的字符串的长度 len 的值不同,就可以判断这两个字符串不相同,不用再去比较两个字符串存储的具体数据。

如果 len 的值相同,再去判断两个字符串的指针是否指向同一块内存,如果 len 的值相同,并且指针指向同一块内存,则可以判断两个字符串相同。但是如果 len 的值相同,而指针不是指向同一块内存,那么还需要继续去比较两个字符串的指针指向的字符串数据是否相同。

字符串拼接

在 Golang 语言中,关于字符串拼接有多种方式,分别是:

  • 使用操作符
  • 使用
  • fmt.Sprintf
  • 使用
  • bytes.Buffer
  • 使用
  • strings.Join
  • 使用
  • strings.Builder

其中使用操作符是最易用的,但是它不是最高效的,一般使用场景是用于已知需要拼接的字符串的长度。

使用 拼接字符串,性能是最差的,但是它可以格式化,所以一般使用场景是需要格式化拼接字符串。

fmt.Sprintf

使用 和使用 的性能比较接近,性能最高的字符串拼接方式是使用 。

bytes.Buffer

strings.Join

strings.Builder

我准备对 的字符串拼接方式多费些笔墨。

strings.Builder

Golang 语言标准库 strings 中的 Builder 类型,用于在 Write 方法中有效拼接字符串,它减少了数据拷贝和内存分配。

Builder 结构体中包含两个字段,分别是 addr 和 buf,字段 addr 是指针类型,字段 buf 是字节切片类型,但是它的值仍然不允许被修改,但是字节切片中的值可以被拼接或者被重置。

Builder 提供了一系列 Write* 拼接方法,这些方法可以用于把新数据拼接到已存在的数据的末尾,同时如果字节切片的容量不够用,可以自动扩容。需要注意的是,只要触发扩容,就会涉及内存分配和数据拷贝。自动扩容规则和切片的扩容规则相同。

除了自动扩容,还可以手动扩容,Builder 提供的 Grow 方法,可以根据 int 类型的传参,扩充字节数量。因为扩容操作,会涉及内存分配和数据拷贝,所以调用 Grow 方法手动扩容时,Golang 也做了优化,如果当前字节切片的容量剩余字节数小于或等于传参的值, Grow 方法将不会执行扩容操作。手动扩容规则是原字节切片容量的 2 倍加上传参的值。

Builder 类型还提供了一个重置方法 Reset,它可以将 Builder 类型的变量重置为零值。被重置后,原字节切片将会被垃圾回收。

在了解完上述 Builder 的介绍后,相信读者已对 Builder 有了初步认识。下面我们通过代码看一下预分配字节数量和未分配字节数量的区别:

阅读上面这段代码,可以发现调用 Grow 方法,预分配字节数量比未预分配字节数量的字符串拼接效率高。我们在可以预估字节数量的前提下,尽量使用 Grow 方法预先分配字节数量。

注意:第一,Builder 类型的变量在被调用之后,不可以再被复制,否则会引发 panic。第二,因为 Builder 类型的值不是完全不可修改的,所以使用者需要注意并发安全的问题。
字符串和字节切片互相转换

因为切片类型除了只能和 nil 做比较之外,切片类型之间是无法做比较操作的。如果我们需要对切片类型做比较操作,通常的做法是先将切片类型转换为字符串类型。但是因为 string 类型是只读的,不可修改的,所以转换操作会涉及内存分配和数据拷贝。

为了提升转换的性能,唯一的方法就是减少或者避免内存分配的开销。在 Golang 语言中,运行时对二者的互相转换也做了优化,感兴趣的读者可以阅读 runtime 中的相关源码:

/usr/local/go/src/runtime/string.go

但是,我们还可以继续优化,实现零拷贝的转换操作,从而避免内存分配的开销,提升转换效率。

先阅读 reflect 中 StringHeader 和 SliceHeader 的数据结构:

阅读上面这段代码,我们可以发现 StringHeader 和 SliceHeader 的字段只缺少一个表示容量的字段 Cap,二者都有指向存储数据的字节数组的指针和长度。我们只需要通过使用 获取内存地址,就可以实现在原内存空间修改数据,避免了内存分配和数据拷贝的开销。

unsafe.Pointer

因为 StringHeader 比 SliceHeader 缺少一个表示容量的字段 Cap,所以通过 将 转换为 没有问题,但是反之就不行了。我们需要补上一个 Cap 字段,并且将字段 Len 的值作为字段 Cap 的默认值。

阅读上面这段代码,我们可以发现通过使用 把字符串转换为字节切片,可以做到零拷贝,str 和 str2 共用同一块内存,无需新分配一块内存。但是需要注意的是,转换后的字节切片仍然不能修改,因为在 Golang 语言中字符串是只读的,通过索引下标修改会引发 panic。

总结

本文我们介绍了怎么高效使用 Golang 语言中的字符串,先是介绍了字符串在 runtime 中的数据结构,然后介绍了字符串拼接的几种方式,字符串与字节切片零拷贝互相转换,还通过示例代码证明了字符串在 Golang 语言中是只读的。更多关于字符串的操作,读者可以阅读标准库 strings 和 strconv 了解更多内容。

原文链接:https://mp.weixin.qq.com/s/Na5hQiuggifsHOEnw-2oXA

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
沉默1日,大陆居然公布统一后安排,台学者:台军就该灭了台独

沉默1日,大陆居然公布统一后安排,台学者:台军就该灭了台独

零洛浮华
2026-06-22 11:26:07
山姆买的啤酒罐口长毛了 消费者拒绝代金券补偿,厂家出面赔付千元

山姆买的啤酒罐口长毛了 消费者拒绝代金券补偿,厂家出面赔付千元

信网
2026-06-25 21:35:18
保姆不要工资,半夜主人听到厨房磨刀,推开门看到保姆,主人吓瘫

保姆不要工资,半夜主人听到厨房磨刀,推开门看到保姆,主人吓瘫

悬案解密档案
2026-03-31 09:16:06
偶遇前婆婆翻垃圾桶,我塞她六万块,五天后前夫送来袋子手直抖

偶遇前婆婆翻垃圾桶,我塞她六万块,五天后前夫送来袋子手直抖

墨染尘香
2026-06-25 18:14:59
一天被“坑”三次!韩国队掉到“第三名排行榜”第6位,出线形势急转直下

一天被“坑”三次!韩国队掉到“第三名排行榜”第6位,出线形势急转直下

红星新闻
2026-06-26 13:13:29
替女上司挡灾缝15针,第二天被开除,收拾东西时她突然叫住我

替女上司挡灾缝15针,第二天被开除,收拾东西时她突然叫住我

千秋文化
2026-06-23 20:03:54
中印风向有变?新德里突然对华大献殷勤,莫迪的意图中方很清楚

中印风向有变?新德里突然对华大献殷勤,莫迪的意图中方很清楚

调侃国际观点
2026-06-26 19:19:20
尼科-威廉斯:刻意避开阿根廷没意义;希望找回欧洲杯的自己

尼科-威廉斯:刻意避开阿根廷没意义;希望找回欧洲杯的自己

懂球帝
2026-06-26 00:57:19
多家水果电商称遭淮北一男子网图骗赔“白嫖”榴莲 当地警方已立案调查

多家水果电商称遭淮北一男子网图骗赔“白嫖”榴莲 当地警方已立案调查

大风新闻
2026-06-26 15:53:09
藏得太深!南京不声不响的良心医院,治疗效果不输大医院

藏得太深!南京不声不响的良心医院,治疗效果不输大医院

华庭讲美食
2026-06-26 18:03:31
倒数第二签仨老将!李昂刘彬彬空降武汉,三镇这波保级操作太狠了

倒数第二签仨老将!李昂刘彬彬空降武汉,三镇这波保级操作太狠了

宝哥精彩赛事
2026-06-26 18:50:18
胡先煦连名带姓全丢了!网友:还以为他本名叫谭尽

胡先煦连名带姓全丢了!网友:还以为他本名叫谭尽

喜欢历史的阿繁
2026-06-26 17:00:08
杀人诛心:乌军网上公开俄军武器全部弱点,32国实验室连夜抄作业

杀人诛心:乌军网上公开俄军武器全部弱点,32国实验室连夜抄作业

咣当地球
2026-06-23 15:59:08
住建部发话了:楼龄20年以上的老房子,这一次真轮到咱了!

住建部发话了:楼龄20年以上的老房子,这一次真轮到咱了!

蜉蝣说
2026-06-12 11:32:10
安切洛蒂战术助维尼修斯爆发!巴西队真正考验来了!

安切洛蒂战术助维尼修斯爆发!巴西队真正考验来了!

李喜林篮球绝杀
2026-06-26 17:25:05
炸锅!七宝中学直接逆袭冲进前三,8人上榜屏蔽线!

炸锅!七宝中学直接逆袭冲进前三,8人上榜屏蔽线!

马蹄烫嘴说美食
2026-06-25 12:29:05
白色保罗衫加咖色微喇瑜伽裤,熟女人妻简约穿搭勾勒饱满丰腴曲线

白色保罗衫加咖色微喇瑜伽裤,熟女人妻简约穿搭勾勒饱满丰腴曲线

只要高兴就好
2026-06-23 09:03:11
杨瀚森赛季原形毕露,发牌本事曾骗惨开拓者与球迷

杨瀚森赛季原形毕露,发牌本事曾骗惨开拓者与球迷

林子说事
2026-06-26 08:05:13
为什么大获全胜的歼-10卖不出去,一败涂地的阵风却销量火爆?

为什么大获全胜的歼-10卖不出去,一败涂地的阵风却销量火爆?

基斯默默
2026-05-28 11:06:03
特朗普和美议员爆发争吵,激烈互吼:临走撂下一句狠话

特朗普和美议员爆发争吵,激烈互吼:临走撂下一句狠话

菁菁子衿
2026-06-25 15:22:01
2026-06-26 20:47:00
马哥Linux云计算
马哥Linux云计算
知识萃取精华,赋能技术人生
93文章数 1053关注度
往期回顾 全部

科技要闻

拿了500亿的梁文锋,只挖地基,不信销售

头条要闻

女生被男友劫持到天台坠亡 初次约会就被灌醉发生关系

头条要闻

女生被男友劫持到天台坠亡 初次约会就被灌醉发生关系

体育要闻

我在世界杯的每次奔跑,都为了证明你没看错

娱乐要闻

玥儿不回北京,马筱梅解释后妈身份

财经要闻

悬在科技头上的达摩克利斯之剑

汽车要闻

老板们的新座驾!65万元起,尊界V800/V680开启预订

态度原创

旅游
家居
时尚
健康
军事航空

旅游要闻

网传“甘南所有景点免费”为假消息 门票减免有限定

家居要闻

绿意盎然 自然之境

夏天衣服完全没必要越买越多,准备几件V领上衣,简单又显瘦

“无糖汤圆”是否隐藏着健康陷阱?

军事要闻

伊朗:驶离指定航线船舶不享有安全保障

无障碍浏览 进入关怀版