Go 语言
声明、类型、语句与控制结构
string 实现原理

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