string 类型的数据是不可修改的
无论常量还是变量,一旦声明了一个 string 类型的标识符,该标识符所指代的数据在整个程序的生命周期内便无法更改。
这种不可变性是通过内存保护的,底层的数据存储区只能进行只读操作,即使使用 unsafe.Pointer
获取到了底层数据地址,也不能修改。
string 的底层表示
// runtime/string.go
type stringStruct struct {
str unsafe.Pointer
len int
}
从这个结构体可以看出,golang
获取 string 的长度为 时间复杂度的操作。
另外 string 也和 slice 类型类似,类似于值类型,所以即使是巨大的 string 作为参数传递也不会有很大开销。
string 以 UTF8 的编码格式存储在内存中
多行字符串
const s = `好雨知时节,当春乃发生。
随风潜入夜,润物细无声。
野径云俱黑,江船火独明。
晓看红湿处,花重锦官城。`
string 构造效率
Rank(Fastest to slowest) | multipler |
---|---|
strings.Builder with initial size | - |
bytes.Buffer with initial size | ~1.14x |
strings.Join | ~1.16x |
bytes.Buffer | ~1.9x |
+ operator | ~2.1x |
strings.Builder | ~2.2x |
fmt.Sprintf | ~10x |
结论
- 做了初始化的
strings.Builder
,bytes.Buffer
与strings.Join
算第一梯队- 未初始化的
bytes.Buffer
,strings.Builder
与+
操作符算第二梯队,大概慢一倍左右fmt.Sprintf
最慢,差了近一个数量级
string 转换时需要拷贝
由于 string 类型的不可变性,导致 string 转 []byte
或者 []Rune
时都会需要分配内存再拷贝数据,反之也是。
同时为了效率,Golang 对某些特定转换做了特殊的优化。
第一种是由于 []byte
不可比较,而 string
可以比较,所有经常需要将 []byte
转换为 string
做比较,再下面几种情况下调用 string(b)
不会分配新的内存。
string(b)
用在 map 类型的 key 中
b := []byte{'k', 'e', 'y'}
m := make(map[string]string)
m[string(b)] = "value"
m[[3]string{string(b), "key1", "key2"}] = "value1"
string(b)
用在字符串连接语句中
b := []byte{'t', 'o', 'n', 'y'}
s := "hello " + string(b) + "!"
string(b)
用在字符串比较中
s := "tom"
b := []byte{'t', 'o', 'n', 'y'}
if s < string(b) {
...
}
第二种情况是在 for _, v := range []byte(str)
的时候,会直接使用 string 底层的字节数组而不会复制拷贝。