1. 内部结构
type StringView struct {
strBuf uintptr
strLen uint64
//strCap uint64 !不存在cap
}
golang在对于string设计上,把内存分配的控制权完全交给了runtime的相关函数,所以没有strCap的字段。个人认为golang的string,可以对应于C++中的string_view(C++17 后支持),只不过在C++中,最终字符串的内存回收还是交由程序员自己控制。
不妨来做一些测试来验证想法:
1.1 通过unsafe暴力获取
(1)验证赋值行为,代码中s2仅复制了s1中的buf地址和长度
type StringView struct {
strBuf uintptr
strLen uint64
}
func main() {
s1 := "123"
fmt.Println((*StringView)(unsafe.Pointer(&s1)).strBuf)
fmt.Println((*StringView)(unsafe.Pointer(&s1)).strLen)
s2 := s1
fmt.Println((*StringView)(unsafe.Pointer(&s2)).strBuf)
fmt.Println((*StringView)(unsafe.Pointer(&s2)).strLen)
}
结果:
(2) 验证新的字符串赋值,由于Ken Thomp参与了C和Golang的设计,所以猜测"123",“4567”都属于静态常量(直接放在可执行文件的数据段),它们二者之间的地址相距应很小,而且只要程序运行,它们就不会被GC(这点⚠️,仅仅是猜测)。
type StringView struct {
strBuf uintptr
strLen uint64
}
func main() {
s1 := "123"
fmt.Println((*StringView)(unsafe.Pointer(&s1)).strBuf)
fmt.Println((*StringView)(unsafe.Pointer(&s1)).strLen)
s2 := s1
fmt.Println((*StringView)(unsafe.Pointer(&s2)).strBuf)
fmt.Println((*StringView)(unsafe.Pointer(&s2)).strLen)
s2 = "4567"
fmt.Println((*StringView)(unsafe.Pointer(&s2)).strBuf)
fmt.Println((*StringView)(unsafe.Pointer(&s2)).strLen)
}
猜想大致正确,它们应该都在较低的数据段。
(3) 验证运行时的拷贝行为
package main
import (
"fmt"
"unsafe"
)
type StringView struct {
strBuf uintptr
strLen uint64
}
func main() {
s1 := "123"
fmt.Println((*StringView)(unsafe.Pointer(&s1)).strBuf)
fmt.Println((*StringView)(unsafe.Pointer(&s1)).strLen)
s2 := s1
fmt.Println((*StringView)(unsafe.Pointer(&s2)).strBuf)
fmt.Println((*StringView)(unsafe.Pointer(&s2)).strLen)
s2 = "4567"
fmt.Println((*StringView)(unsafe.Pointer(&s2)).strBuf)
fmt.Println((*StringView)(unsafe.Pointer(&s2)).strLen)
s3 := s2 + "78910"
fmt.Println((*StringView)(unsafe.Pointer(&s3)).strBuf)
fmt.Println((*StringView)(unsafe.Pointer(&s3)).strLen)
s4 := s1 + s2
fmt.Println((*StringView)(unsafe.Pointer(&s4)).strBuf)
fmt.Println((*StringView)(unsafe.Pointer(&s4)).strLen)
}
可以看到,s3、s4在使用拼接行为后,在底层创建了新的字符串,且地址位较高,极又可能是运行时分配在了堆上。
2. 使用reflect后的相关行为
2.1 一些基本的参数
package main
import (
"fmt"
"reflect"
)
func main() {
s1 := "123"
s1Value := reflect.ValueOf(&s1).Elem()
fmt.Println(s1Value.Type().Kind())
fmt.Println(s1Value.CanSet())
}
在被解析为reflect.Value后,string类型并不能使用Set,其他的属性,都符合正常人类认识,不一一列出。
2.2 变成可以使用Set的方法
package main
import (
"fmt"
"reflect"
)
func main() {
s1 := "123"
s1Value := reflect.ValueOf(&s1).Elem()
fmt.Println(s1Value.Type().Kind())
fmt.Println(s1Value.CanSet())
}
改成指针,再取出来就可以,不得不说,go这儿的设计,就很脱裤子放屁,都已经深入到反射了,还那么多限制。
最后验证一下:
package main
import (
"fmt"
"reflect"
"unsafe"
)
type StringView struct {
strBuf uintptr
strLen uint64
//strCap uint64 !不存在cap
}
func main() {
s1 := "123"
s1Value := reflect.ValueOf(&s1).Elem()
fmt.Println("s1的原始底层地址、长度:")
fmt.Println((*StringView)(unsafe.Pointer(&s1)).strBuf)
fmt.Println((*StringView)(unsafe.Pointer(&s1)).strLen)
s2 := "456"
fmt.Println("s2的底层地址、长度:")
fmt.Println((*StringView)(unsafe.Pointer(&s2)).strBuf)
fmt.Println((*StringView)(unsafe.Pointer(&s2)).strLen)
s2Value := reflect.ValueOf(&s2).Elem()
s1Value.Set(s2Value)
fmt.Println(s1)
fmt.Println("s1被设置后的值:")
fmt.Println(s1)
fmt.Println("s1被设置后的底层地址、长度:")
fmt.Println((*StringView)(unsafe.Pointer(&s1)).strBuf)
fmt.Println((*StringView)(unsafe.Pointer(&s1)).strLen)
}