unsafe高效转换string和slice
一、string与[]byte的如何高效转换
a:="hello world"
b:= []byte(a)
string的内存布局:data+len
slice的内存布局:data+len+cap
二者都是由一个底层数组储存变量数据,而类型本身只记录这个数组的起始位置。如果采用强制类型转换的方式把a转为b,那么就会重新分配b使用的底层数组,然后把a的底层数组拷贝到b的底层数组,如果字符串内容很多,既费时也浪费内存
方案一:unsafe指针类型转换
unsafe可把任意一个指针类型转换为unsafe.Pointer类型,再把unsafe.Pointer转为任意指针类型
s := "hello"
var b []byte
tmp := (*string)(unsafe.Pointer(&b)) //指针运算
*tmp = s // 完成data部分 拷贝
// 完善 slice容量
// startAddr := (uintptr)(unsafe.Pointer(&b)) + 16 //64位系统下 b的内存起始地址
// 拿到b的容量的指针 错误操作
// tmp2 :=(*int)(unsafe.Pointer(startAddr))
// 获取偏移量
tmp2 := (*int)(unsafe.Pointer((uintptr)(unsafe.Pointer(&b)) + uintptr(16)))
*tmp2 = len(b)
fmt.Println("b>>", b, len(b), cap(b)) //b>>
方案二:反射+unsafe
unsafe还有一个unsafe.Offsetof方法可以获得结构体中某个字段距离结构体起始地址的偏移值,这样就可以确定结构体成员正确的位置了
s := "bob"
var b []byte
// b公用s的底层数组
tmp := (*string)(unsafe.Pointer(&b))
*tmp = s
//转化为SliceHeader结构体
bPtr := (*reflect.SliceHeader)(unsafe.Pointer(&b))
// 然后获取Cap字段在结构体内的偏移值:
tmp2 := (*int)(unsafe.Pointer((uintptr)(unsafe.Pointer(&b)) + unsafe.Offsetof(bPtr.Cap)))
*tmp2 = len(s)
fmt.Println(b, "len:", len(b))
方案三:reflect.SliceHeader && StringHeader
s := "bob"
var b []byte
fmt.Println(b, "len:", len(b))
sliceHead := (*reflect.SliceHeader)(unsafe.Pointer(&b))
strHeader := (*reflect.StringHeader)(unsafe.Pointer(&s))
sliceHead.Cap = strHeader.Len
sliceHead.Data = strHeader.Data
sliceHead.Len = strHeader.Len
fmt.Println(b, "len:", len(b))
注意:关于string
go语言中的string变量的内容默认是不会被修改的,但是可以通过给string变量整体赋新值的方式改变它的内容时,实际上会重新分配它的底层数组。
而string类型字面量的底层数组会被分配到只读数据段。在上面的例子中,b复用了a的底层数组,所以就不能b[0]='k'
修改b的内容了,否则执行阶段会发生错误。unexpected fault address 0x10cb3e8
但是运行时动态拼接而成的string变量,他的底层数组不在只读数据段,而是由go语言在语法层面阻止了对字符串内容的修改行为
a := "world"
b := "Hello" + a
c[0]='h' cannot assign to c[0] (value of type byte)
若我们利用unsafe让一个[]byte复用这个字符串c的底层数组,就可以绕过Go语法层面的限制,修改底层数组的内容了。
a := "world"
// c := "Hello" + a
c :=fmt.Sprintf("%s %s","Hello",a)
var s []byte
strHeader := (*reflect.StringHeader)(unsafe.Pointer(&c))
sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&s))
sliceHeader.Data = strHeader.Data
sliceHeader.Len = strHeader.Len
sliceHeader.Cap = strHeader.Len
s[0] = 'h'
fmt.Println(c) //“hello world”
注释:Golang 中"" 是字符串,’’ 是byte。所以第12行是单引号 ’ '