当你使用要对一个变量从一个类型强制转换成另一个类型,其实都会发生内存的拷贝,而这种拷贝会对性能有所影响的,因此如果可以在转换的时候避免内存的拷贝就好了。
庆幸的是,在一些特定的类型下,这种想法确实是可以实现的。
比如将字符串转成 []byte 类型。
// string to []byte
s1 := "hello"
b := []byte(s1)
// []byte to string
s2 := string(b)
不发生内存拷贝的方法如下:
func main() {
msg1 := "hello"
sh := *(*reflect.StringHeader) (unsafe.Pointer(&msg1))
bh := reflect.SliceHeader{
Data: sh.Data,
Len: sh.Len,
Cap: sh.Len,
}
msg2 := *(*[]byte)(unsafe.Pointer(&bh))
fmt.Printf("%v", msg2)
}
要理解这一段代码,我们需要3个知识点:
- 一种定义变量怪异方法
- 字符串的底层数据结构
- 切片的底层数据结构
一种定义变量的怪异方法
name := (string) ("我真的很喜欢你")
那么我们再反过来看这一段代码:
tmp := *(*reflect.StringHeader)(unsafe.Pointer(&msg))
由于第一个括号里面是一个指针类型,那么第二个括号里面肯定是指针的值。
而通过 unsafe.Pointer
就可以将 &msg
指针的内存地址取出来。
两个括号合起来就是,声明并定义了一个 *reflect.StringHeader
类型的指针变量,对应的指针值还是原来 msg1 的内存地址。
那最前面的的那个那个 *
,大家应该都知道,是从*reflect.StringHeader
类型的指针变量中取出值。
然后我们来看一下StringHeader
这个结构体:
type StringHeader struct {
Data uintptr
Len int
}
同样的,SliceHeader
则是切片的底层数据结构
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
对咯,只要把 StringHeader
里的 Data 塞给 SliceHeader
里的 Data,再把 SliceHeader
里的 Len 塞给 SliceHeader
里的 Len 和 Cap ,就多费任何的空间创造出一个新的变量。
bh := reflect.SliceHeader{
Data: sh.Data,
Len: sh.Len,
Cap: sh.Len,
}
最后再把 SliceHeader
通过上面的强制转换方法,再转成 []byte
就可以了,中间就不会有任何的内存拷贝的过程。