

本笔记旨在记录Go语言中的底层内容学习过程,仅代表个人学习记录,阅读请注意鉴别版本及特性。
常量定义
1 | tmpStringBufSize = 32 |
Golang String 结构体
1 | type stringStruct struct { |
Golang的String结构体是由两个成员组成,str是一个unsafe.Pointer,主要指向的是一个字节序列的指针,该指针指向的是字符串序列内存地址的起始字节,该内存区域是只读的,也是Go语言中字符串不可变(immutable)特性的根本原因。
由于Golang使用的是非定长字节码来表示字符串的内容,非直接读取字节数目可以获取到字符串的长度,故此处追加了一个len成员来表示字节序列的长度,用以实现O(1)级别的字符串长度获取len(str)
,而不需要像C语言一样遍历整个字符串一直到\0
为止
1 | tmpBuf [tmpStringBufSize]byte |
tmpBuf
是栈上临时缓冲区的具体类型,它是一个 32 字节的数组。
String类型的底层内存布局与stringStruct完全一致,运行时通过unsafe包来进行转换,函数如下:
1 | func stringStructOf(sp *string) *stringStruct { |
该函数接受一个string
类型的指针*string
,然后通过unsafe.Pointer
强制转换为*stringStruct
类型的指针。其内存布局是等价的,故允许运行时直接操作字符串的内部指针和长度。
字符串拼接
1 | // concatstrings 实现了Go语言中的字符串拼接功能 x+y+z+... |
执行逻辑:
- 遍历一次
a
,计算出所有非空字符串的总长度l
和数量count
。 - 优化1:如果最终只有一个非空字符串,并且这个字符串本身不在栈上(或者结果允许在栈上),就直接返回这个字符串,避免任何内存分配和拷贝。
- 调用
rawstringtmp
分配内存。这个函数会优先尝试使用传入的buf
。如果l
太大,rawstringtmp
会在堆上分配内存。 - 将
a
中的所有字符串内容依次拷贝到新分配的内存中。 - 返回新生成的字符串。
以下数个concatstring函数是对特殊数量的特化版本,分别用于拼接 2、3、4、5 个字符串。
1 | func concatstring2(buf *tmpBuf, a0, a1 string) string { |
编译器会智能地选择调用这些函数,而不是通用的 concatstrings
。这样做的好处是避免了创建一个 []string
切片以及相关的内存分配和开销,性能更好。如:
1 | // cmd/compile/internal/walk |
concatbytes同理,但返回值是[]byte切片而不是string,后续需要使用字节切片时可以减少一次string到[]byte的转换。其同样拥有类似concatstrings的2,3,4,5特化版本。
1 | func concatbytes(buf *tmpBuf, a []string) []byte { |
类型转换
下列函数实现了 string
, []byte
, []rune
之间的转换。
func slicebytetostring(buf *tmpBuf, ptr *byte, n int) string
string(byteSlice)
语法的底层实现。它将一个字节切片转换为一个字符串,会拷贝数据,分配一块新的内存,并将 ptr
指向的 n
个字节拷贝过去,然后用这块新内存生成一个字符串。这是保证字符串不可变性的关键。同样会尝试使用 buf
来进行栈上分配。另外,对于长度为 1 的字节切片,它有一个特殊的微优化,会从一个静态表中直接返回字符串,避免分配。
func stringtoslicebyte(buf *tmpBuf, s string) []byte
[]byte(s)
语法的底层实现。将字符串转换为字节切片。会拷贝数据,分配新的内存用于 []byte
,并将字符串的内容拷贝过去。
func slicebytetostringtmp(ptr *byte, n int) string
这是一个不安全的、零拷贝的转换函数,不会拷贝数据。它也用于实现 string(byteSlice)
,但仅在编译器能确保安全性的特殊场景下使用(例如,map[string(k)],其中 k 是 []byte)。直接使用 ptr
指针和长度 n
来创建一个 string
头信息。这意味着返回的 string
和原始的 []byte
共享同一块底层内存。如果原始的 []byte
在之后被修改,那么这个 “不可变” 的 string
的内容也会随之改变,这违反了 Go 的基本原则。因此,它的使用被严格限制在编译器的内部优化中。
func slicerunetostring(buf *tmpBuf, a []rune) string
func stringtoslicerune(buf *[tmpStringBufSize]rune, s string) []rune
实现 string
和 []rune
之间的转换。因为 rune
(本质是 int32
) 和 byte
之间需要进行 UTF-8 编码和解码,所以这两个转换比 []byte
的转换要复杂。slicerunetostring
:先遍历一次 []rune
计算出编码成 UTF-8 后需要的总字节数,分配内存,然后再遍历一次进行编码和拷贝,而stringtoslicerune
:先遍历一次字符串计算出 rune
的数量,分配内存,然后再遍历一次进行解码并填充 []rune
切片。
底层内存操作和辅助函数
1 | func rawstring(size int) (s string, b []byte) { |
rawstring
函数在堆上分配一块指定大小的内存,并同时返回一个指向这块内存的 string
和一个 []byte
。通常会通过返回的 []byte
来填充内容,然后丢弃掉b
使用s
。
rawbyteslice
函数直接在堆上分配指定容量的 []byte
或 []rune
切片。
rawruneslice
分配一个非0值rune
切片。