unsafe

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行是单引号 ’ '

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值