string 结构
Go 的字符串,本质结构是reflect.StringHeader。
该结构位于源码 go1.16:reflect/value.go:1983
type StringHeader struct {
Data uintptr
Len int
}
此结构是运行时字符串的表示,其中包含指向字节数组的指针Data和数组的大小Len,Data需要保留一个单独的、正确类型的指向底层数据的指针,防止垃圾回收。
虽然相同的字符串,指针可以指向不同的地址,但是他们的reflect.StringHeader,其内部Data指针实际上都指向相同的字节数组。
用下面的代码来说明:
package main
import (
"fmt"
"runtime/debug"
)
func main() {
a := "a"
b := "a"
// 两个相同字符串地址并不相同
fmt.Printf("a: %p, b %p\n", &a, &b)
// 两个相同字符串Data地址相同
printStack(a)
printStack(b)
}
func printStack(s string) {
println(string(debug.Stack()))
}
➜ string go run -gcflags="-N -l" main.go
a: 0xc000096220, b 0xc000096230
goroutine 1 [running]:
runtime/debug.Stack(0x0, 0x0, 0xc000092ea8)
/usr/local/go/src/runtime/debug/stack.go:24 +0x9f
main.printStack(0x10c9655, 0x1)
/Users/mygo/ch/gist/g21/string/main.go:19 +0x26
main.main()
/Users/mygo/ch/gist/g21/string/main.go:14 +0x196
goroutine 1 [running]:
runtime/debug.Stack(0xc0000bc000, 0x113, 0x113)
/usr/go/src/runtime/debug/stack.go:24 +0x9f
main.printStack(0x10c9655, 0x1)
/Users/mygo/ch/gist/g21/string/main.go:19 +0x26
main.main()
/Users/mygo/ch/gist/g21/string/main.go:15 +0x1bd
string interning
String Interning是一种在内存中存储每个唯一字符串的一个副本的技术。 它可以显着降低存储许多重复字符串的应用程序的内存用法。
需要注意的是 Go 的 string intern 仅仅针对的是编译期可以确定的字符串常量,如果是运行期间产生的字符串则不能被内部化。
// 可以被 intern
s1 := "7"
// 不能被 intern
s2 := strconv.Itoa(7)
因为string的指针指向的内容是不可以更改的,所以每更改一次字符串,就得重新给StringHeader.Data分配一次内存,之前分配空间需要由GC判断回收,这是导致大量string操作低效的根本原因。
我们可以试着来绕过其限制,来完成一个可以内部化所有字符串的实现。首先我们需要一个 pool,把所有的字符串都放到这个 pool 里,只要字符串在这个 pool 里只有一份(例如 Map 就是一个非常好的选择),就可以认为已经被 intern 了。参考:String interning in Go
package main
import (
"fmt"
"reflect"
"strconv"
"unsafe"
)
// stringptr returns a pointer to the string data.
func stringptr(s string) uintptr {
return (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
}
type stringInterner map[string]string
func (si stringInterner) Intern(s string) string {
if interned, ok := si[s]; ok {
return interned
}
si[s] = s
return s
}
func main() {
si := stringInterner{}
str1 := "7"
str2 := strconv.Itoa(7)
fmt.Println(stringptr(str1) == stringptr(str2)) // false
s1 := si.Intern("12")
s2 := si.Intern(strconv.Itoa(12))
fmt.Println(stringptr(s1) == stringptr(s2)) // true
}
string常见用法
字符串常量是不可修改的,字符串变量是可以修改的。
func main() {
const c = "c"
a := "a"
c = a // Cannot assign to c
a = c
}
比较大小
可以有=,!= 操作
实际上 Go 对比两个字符串是否相等,首先会对比其长度,长度不同自然是不同的串,时间复杂度为 O(1);如果长度相同,再对比其底层字节数组地址,地址相同肯定是相同的串,时间复杂度仍然可以认为是 O(1);如果地址不同,则需要逐个对比字节,那么时间复杂度也就退化为了 O(N)。
相加操作
- 使用
+ - strings Join/Buffer
- fmt Sprint/Sprintf
- bytes.Buffer
转换操作
string and byte
a:="hello world"
b:=[]byte(a)
a=string(b)
string and rune
a:="hello world"
b:=[]rune(a)
a=string(b)
string to int
使用strconv的方法