string 实现原理
类型
在 Go 语言中,无论是字符串常量、字符串变量,还是代码中出现的字符串字面量,它们的类型统一设置为 string。
const s = "hello"
func main() {
var s1 string = "hello"
fmt.Printf("%T\n", s) // string
fmt.Printf("%T\n", s1) // string
fmt.Printf("%T\n", "hello") // string
}
不可变的
一旦声明了一个 string 类型的标识符,无论是常量还是变量,该标识符所指代的数据在整个程序员的生命周期内都无法更改。
如果我们试图通过 unsafe
指针指向 string 在运行时内部表示结果中的数据存储块地址,然后通过指针修改那块内存中的存储数据,便会得到 SIGBUS
运行时报错。
零值可用
var s string
fmt.Println(s) // ""
fmt.Println(len(s)) // 0
获取长度的时间复杂度 O(1)
因为 string 不可变,一旦有了初值,那块数据就不会改变,其长度也不会改变。 Go 将长度作为一个字段存储在运行时的 string 类型的内部表示结构中。
支持 +/+= 操作符
s := "hello"
s += " world"
fmt.Println(s) // hello world
s = s + " world"
fmt.Println(s) // hello world world
支持比较操作符
Go string 支持各种比较关系操作符:==
、!=
、<
、<=
、>
、>=
。
// ==
s1 := "hello"
s2 := "he" + "llo"
fmt.Println(s1 == s2) // true
// !=
s1 = "Go"
s2 = "C"
fmt.Println(s1 != s2) // true
// < 和 <=
s1 = "abc"
s2 = "abd"
fmt.Println(s1 < s2) // true
fmt.Println(s1 <= s2) // true
// > 和 >=
s1 = "abc"
s2 = "abd"
fmt.Println(s1 > s2) // false
fmt.Println(s1 >= s2) // false
支持非 ASCII 字符
Go 语言默认采用 Unicode 字符集。 Unicode 字符集是目前市面上最为通用的字符集,它包含了世界上几乎所有的字符,包括中文、日文、韩文等。
Go 字符串的每个字符都是一个 Unicode 字符,并且这些 Unicode 字符是以 UTF-8 编码格式存储在内存中。
支持多行字符串
Go 语言通过反引号构造“所见即所得”的多行字符串。
s := `hello
world`
内部表示
Go 语言的 string 类型的内部表示结构如下:
type stringStruct struct {
str unsafe.Pointer
len int
}
我们可以看到 string 类型也是一个描述符,它本身并不真正存储数据,而是由一个指向底层存储的指针和一个长度字段组成。
string 在运行时的表示可以得到这样的一个结论:直接将 string 类型通过函数、方法参数传入也不会有太多的损耗,因为传入的仅仅只是一个描述符,而不是真正的字符串数据。
高效构造
Go string 原生支持 +/+=
操作符来连接多个字符串,这是最自然和开发体验最好的一种,但 Go 还提供了其他一些构建字符串的方法:
- 使用
bytes.Buffer
类型 - 使用
strings.Join
函数 - 使用
strings.Builder
类型 - 使用
fmt.Sprintf
函数
它们的性能差异:strings.Builder
> bytes.Buffer
= strings.Join
> fmt.Sprintf
高效转换
string 和 []rune
、[]byte
是可以双向转换的。
无论是 string 转 slice,还是 slice 转 string,都需要付出代价,就是因为 string 不可变,运行时要为转换后的类型重新分配内存。
想要高效地进行转换,唯一的方法就是减少甚至避免额外的内存分配操作。
slice 临时转 string 有这样的优化:string([]byte)
和 string([]rune)
都会直接返回一个 string 类型的值,而不会重新分配内存。
一些使用场景:
// map
b := []byte{'h', 'e', 'l', 'l', 'o'}
m := make(map[string]int)
m[string(b)] = 1
println(m) // map[hello:1]
// 字符串连接
b := []byte{'w', 'o', 'r', 'l', 'd'}
s := "hello " + string(b) + "!"
println(s) // hello world!
// 字符串比较
s := "hello"
b := []byte{'h', 'e', 'l', 'l', 'o'}
println(s == string(b)) // true