【Go笔记】字符串
字符串的本质
- 一般的字符串分为编译时指定长度和动态长度两种,Go使用的是前者
- 字符串的终止有两种方式,一种是C中的隐式申明,以
\0
结尾;一种是Go中的显示声明 - Go中的字符串本质上是字符数组,可以用下标访问,但是不能被修改
- 空间方面,一般字符占1字节,特殊字符如中文,日文等占3个字符
符文类型 (rune)
- 设计者认为,因为有些字符非常相似,用于表示字符串可能会有歧义,而其不同的本质是编码之后的整数,因此使用符文 (rune) 类型来区分字符串中的字符
- rune其实是int32的别称,代表字符编码之后的整数
- 用range轮询字符串的时候,轮询的不是单字节,而是具体的rune,因此index不会是一个一个一个一个一个一个一个一个
- printf中
%#U
可以打印符文数十六进制的Unicode编码方式和字符形状
字符串底层原理
- 语法解析阶段,字符串被标记成
StringLit
的Token被传到下一个阶段 - 语法分析阶段,采取递归下降的方式读取UTF-8字符,单撇号或双引号是字符串的标识
- 抽象语法树阶段,如果是两个字符串相加的话,中间的加号Op会被解析为
OADDSTR
- 两个字符串常量相加会发生在语法分析阶段,调用
noder.sum
函数,将各字符串常量放在字符串数组中,然后使用strings.Join
完成拼接 - 字符串变量的拼接实在运行时完成,在语法分析阶段会做一些检查之类的工作
- 运行时字符串拼接是找到一个大空间,通过内存复制的方式将两个字符串复制到里面
- 通过使用
concatstring{2, 3, 4, 5}
来指定性优化2,3,4,5个字符串的拼接 - 拼接过程中只分配一块内存,缓冲区够用(拼接后字符串小于32字节)的就从缓冲区上切一块下来
- 通过
unsafe
手段,可以在运行时层面让string和[]byte指向同一块内存 - 字符串和字符数组之间可以相互转换,并不是简单地指针引用,而是涉及到了复制。当内存大于32字节的时候,还需要申请堆内存,在密集转换的时候,需要评估性能损耗
总结
- 字符串存储于静态存储区,只能被访问而不能被修改和国运
- 存储使用UTF-8,与Go文件的编码方式相同
- 引入rune类型来消除字符歧义,是4字节的int32整数
- 字符串常量拼接发生在编译时,变量拼接发生在运行时
- 拼接后的字符串小于32时直接调用临时缓存使用,大于32字节时需要在堆区分配内存,有性能损耗
- 字符串和字符数组之间的相互转换涉及到复制,在频繁转换的时候需要考虑到性能损耗
src/runtime/string.go
,syntax/scanner.go
【Go笔记】字符串
https://study.0x535a.cn/go-note/go-string/