这几天发现go的字符串还是有点意思的,go的这几个基本数据结构的底层看来还是要好好地学习一波
零拷贝?但是得拷贝
这个要从string的底层说起,日常我们在使用string的时候,一旦涉及对内容修改,无一例外地就会想到想转成[]byte类型
// StringHeader is the runtime representation of a string.
// It cannot be used safely or portably and its representation may
// change in a later release.
// Moreover, the Data field is not sufficient to guarantee the data
// it references will not be garbage collected, so programs must keep
// a separate, correctly typed pointer to the underlying data.
type StringHeader struct {
Data uintptr
Len int
}
// SliceHeader is the runtime representation of a slice.
// It cannot be used safely or portably and its representation may
// change in a later release.
// Moreover, the Data field is not sufficient to guarantee the data
// it references will not be garbage collected, so programs must keep
// a separate, correctly typed pointer to the underlying data.
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
其中,第一个是字符串的底层结构,本质就是一个结构体,结构体中的成员是一个指向底层[]byte的指针以及长度,通过这个两个元素即可完成一个字符串的定位
第二个是所有切片类型的底层结构,多了一个底层数组的容量
从结构和功能上看,其实这两个底层十分相似,实际上,确实可以通过unsafe包对指针的操作来完成直接[]byte到string的“去封装和重封装”
func main() {
a := "aaa"
// unsafe.Pointer(&a) : 获得变量的地址
// (*reflect.StringHeader)(unsafe.Pointer(&a)): 把字符串a转成底层结构的形式
ssh := *(*reflect.StringHeader)(unsafe.Pointer(&a))
// 把ssh底层结构体转成byte的切片的指针
// 通过*指向实际的内容
b := *(*[]byte)(unsafe.Pointer(&ssh))
fmt.Printf("%v", b)
fmt.Println("---------------------------------------------------")
}
是不是很便利?
然而,别高兴得太早,一旦你试图改动得到的字节切片,会直接panic
显然,这就要说到go语言永远都是值传递的事了
由底层结构可见,复制一个字符串本质上复制的是结构体,即指针值和长度
所以无论复制几份,得到结果都一样,其保障是底层的那个字节数组永远不会变动
假如一旦可以改动,安全性就无法保障,所有字符串由于引用的同一个底层数组,一瞬间所有复制出来的字符串都会跟着变
其实也和mutex
不能复制有异曲同工之妙,复制会同时复制过去状态,从而导致锁复用出现问题
那话说回来,这个零拷贝既然不能修改,那有个卵用啊!
其实还是有场景的
即便不能修改,至少还能转回去嘛还可以截取
go也有字符常量池?
这个其实发现的十分偶然,参考Java中对字符串的实现
我发现在go中,如果你前后赋值两个完全(运行前而非运行时通过strconv.ItoA(123)这种)一样的字符串,其底层指向的其实同一个字节数组,即便是不同函数也一样!
当然,也有可能是编译器的优化
参考