开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第35天,点击查看活动详情
前言
在上一篇文章 Go常用数据结构的高性能实战—— string 篇(一) 中,我们学习了下 string 的基本结构,并学习了一种提高字符串拼接效率的方式 strings.Join 。今天,我们来了解下 strings.Join 原理以及 string 的可变性。
strings.Join 原理
可以先看看 strings.Join 的源码,如图所示:
- 首先判断切片 elems 的长度,若长度为0,返回空字符串;若长度为 1,则返回 elems 的第一个元素
- 然后计算需要返回的字符串长度 = 分隔符 sep 所需要的长度 + elems 切片中元素的总长度
- 初始化一个 strings.Builder 对象,strings.Builder 中包含操作它的指针以及 []byte 切片
- 将 elems 切片中的每一个元素和分割符 sep 使用切片的 append 方法,写入到 []byte 切片中
通过直接给 byte 切片写入字符串的方式,减少了多次内存分配和内存拷贝,从而提升了字符串拼接的性能;另外值得一提的是,使用 Grow 对 byte 切片预分配 cap ,还可以极大地减少扩容次数。
string 是否可变
跟 Java 的 String 类一样,Go 中的 string 也是不可变的。无法对字符串中字符进行修改或者取地址操作,以下代码是无法编译通过的:
func StringChange() {
var str = "zhongger"
str[0] = 'a'
ptr := &str[0]
}
虽然 string 不可变能够带来一定的安全性,但是如果需要修改字符串的话则会有一定的性能消耗。 strings 包下的 ReplaceAll 方法能够帮助我们实现这个操作。
func StringChange() {
var str = "zhongger"
str = strings.ReplaceAll(str, "z", "a")
fmt.Printf("str is %v", str) // 输出 ahongger
}
为此,我们先看看 strings.ReplaceAll 的实现:
下面的 Builder 对象操作,Grow、WriteString 等方法是不是很熟悉?其实所谓的修改,就是重新分配了内存,并使用一定的逻辑执行 append 操作,将字符串写入到新的 byte 切片中,最后返回新的字符串。
综上所述,string 确确实实是不可变的。
小结
好了,本篇文章主要介绍了 strings.Join 原理以及 string 的可变性。明天,我们将一起来探究下 map 的原理和高性能使用吧~