用法
双引号和反单引号
使用双引号表示时,需要对特殊字符转义,而反单引号不需要对特殊字符转义。
字符串拼接
字符串拼接会触发内存分配及内存拷贝,单行语句拼接多个字符串值分配一次内存。 s=s+"a"+"b"
会计算最终字符串长度后再分配内存。 多个字符串被编译器组织到一个切片中,拼接过程遍历两次切片,第一次获取字符串总长度,据此申请内存,第二次便利拷贝数据到变量。
类型转换
string 和 []byte 可以相互转换,但会发生一次内存拷贝,有一定的开销,在高频场景会成为性能瓶颈,数据库访问,http请求处理等。
[]byte 转 string
- 根据切片长度申请内存空间,假设内存地址为p,长度len。
- 构建string(string.str=p; string.len=len;)
- 拷贝切片数据到新申请的内存地址p.
编译优化
byte切片转换成string的场景很多,出于性能考虑,有时候只是应用在临时需要字符串场景下,转换并不会拷贝内存,而是直接返回一个string,string(string.str)的指针指向切片的内存。
比如 编译器会识别如下临时场景:
- 字符串比较 string(b)==”foo”
- 字符串拼接:“a”+”string(b)”+”c”
- m[string(b)]查找map
由于只是临时把byte切片转换成string,避免了因byte切片内容修改而导致string数据变化的问题,所以不必拷贝内存。
特点
UTF8 编码
string使用8比特字节集合来存储字符,而且存储字符是UTF-8编码。像一个汉子会占用2-3个字节。 如下所示:
s="世界"
for index,value:=range s{
fmt.Printf("index:%d, value: %c\n",index,value)
}
索引的下标和字符是对不上的,字符串的长度是指字节数,而非字符数。
值不可修改
字符串不可以修改,字符串变量可以接受新的字符串值,但不能通过下标方式修改字符串中的值。
数据结构
type stringStruct struct{
str unsafe.Pointer //字符串首地址长度
len int
}
string 结构和slice 只差cap字段,所以也叫做只读切片。
string结构体的str指针指向的是一个字符常量的地址, 这个地址里面的内容是不可以被改变的,因为它是只读的,但是这个指针可以指向不同的地址。 因为字符串作为只读的类型,我们并不会直接向字符串直接追加元素改变其本身的内存空间,所有在字符串上的写入操作都是通过拷贝实现的。
字符串生成时,先构建stringStruct对象,再转换成string. string在runtime包是stringStruct类型,对外呈现string类型。
函数传参 切片和string
string是以值传递的方式传入,由于string底层是结构体值传递方式拷贝了string结构体,结构体地址会发生变化,但是结构体中str指针地址在传参过程中没有发生变化。 str指向的是只读内存,对string字符串操作时产生了内存拷贝,str指针也指向了新的内存地址。
s := "a"
fmt.Printf("%p\n", &s)
b := func(s string) {
fmt.Printf("%p\n", &s)
s = s + "b"
}
b(s)
fmt.Println(s)
-----
0xc000010250
0xc000010260
a
切片作为参数传入时,结构体内部指向切片第一个元素的指针没有发生变化,对切片元素修改时,内存地址不会发生变化。但是进行append操作时,有可能会超出容量,产生内存拷贝导致内存地址发生变化,这种情况下传参前后切片不在指向同一个内存地址。
s := []int{1, 2}
fmt.Printf("%p\n", &s)
b := func(s []int) {
s[0] = 3
fmt.Printf("%p\n", &s)
}
b(s)
fmt.Println(s)
----
0xc00009a018
0xc00009a030
[3 2]
小结
为什么不允许修改字符串
C++ string 本身拥有内存空间,修改string是支持的,但在Go实现中,string包不包含内存空间,只有一个内存的指针。这样做的好处是string变得非常轻量,可以很方便地进行传递而不用担心内存拷贝。
因为string通常指字符串的字面量,字面量存储位置是只读段,而不是堆栈,所以才有了不可修改的约定。
string和[]byte应用场景:
string 场景:
- 需要字符串比较多场景
- 不需要nil字符串
[]byte擅长的场景:
- 修改字符串
- 函数返回值,需要用nil表示含义的场景。
- 需要切片操作
作业
package main
import (
"fmt"
"reflect"
"unsafe"
)
// slice 和 string 的底层结构
type slice struct {
array unsafe.Pointer
len int
cap int
}
type stringStruct struct {
str unsafe.Pointer
len int
}
func set(s string) {
fmt.Println("param s addr:", &s)
s = "456"
}
func setPtr(s *string) {
fmt.Println("param s addr:", s)
*s = "456"
}
func main() {
var a string = "123"
fmt.Println(a)
//stringHeader 也可以由自定义的stringStruct替代
hdr1 := (*reflect.StringHeader)(unsafe.Pointer(&a))
fmt.Println(&a)
fmt.Printf("str:%s, data addr:%d, len:%d\n", a, hdr1.Data, hdr1.Len)
set(a)
fmt.Printf("str:%s, data addr:%d, len:%d\n", a, hdr1.Data, hdr1.Len)
fmt.Println(&a)
setPtr(&a)
fmt.Printf("str:%s, data addr:%d, len:%d\n", a, hdr1.Data, hdr1.Len)
fmt.Println(&a)
}
set和setptr操作输出的结果会是什么?
结果
string a addr: 0xc00010c210
str:123, data addr:4807580, len:3
param s addr: 0xc00010c230
str:123, data addr:4807580, len:3
string a addr: 0xc00010c210
param s addr: 0xc00010c210
str:456, data addr:4807586, len:3
string a addr: 0xc00010c210
文档信息
- 本文作者:Beast
- 本文链接:https://beastpu.github.io/2021/07/13/go-01/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)