版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_30103483/article/details/89492872
截取自我的博客:https://chunlife.top/
摘取自gateway,作者提到过这种方式,也是来源于另一个开源项目,操作方式极其硬核,故收藏了。
String与Slice互转
package hack
import (
"reflect"
"unsafe"
)
// SliceToString slice to string with out data copy
func SliceToString(b []byte) (s string) {
pbytes := (*reflect.SliceHeader)(unsafe.Pointer(&b))
pstring := (*reflect.StringHeader)(unsafe.Pointer(&s))
pstring.Data = pbytes.Data
pstring.Len = pbytes.Len
return
}
// StringToSlice string to slice with out data copy
func StringToSlice(s string) (b []byte) {
pbytes := (*reflect.SliceHeader)(unsafe.Pointer(&b))
pstring := (*reflect.StringHeader)(unsafe.Pointer(&s))
pbytes.Data = pstring.Data
pbytes.Len = pstring.Len
pbytes.Cap = pstring.Len
return
}
这里使用到了slice与string关键字在Go中的本质,直接对关键字的核心构造进行一系列操作,根本不讲Go语言的规矩,算是一种黑魔法
。
string的本质:reflect.StringHeader{}
slice的本质:reflect.SliceHeader{}
go指针的本质:unsafe.Pointer{}、uintptr{}
查看源码,可以找到Go编译器对slice与string的具体解释,均保留有一个指针,用于指向数据真正的地址。
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
SliceHeader是切片的运行时表示。它不能被安全或便携地使用,其表示可能会在以后的版本中更改。此外,数据字段不足以保证它引用的数据不会被垃圾收集,因此程序必须保留一个单独的,正确键入的指向底层数据的指针。
type StringHeader struct {
Data uintptr
Len int
}
StringHeader是字符串的运行时表示形式。
另外,我们看到这俩结构体类似,内存布局也是类似的,只是StringHeader
少了一个字段,但至少内存是对其的,那么?(C语言又在发威了~)
注意:反过来转换不行,StringHeader
对比SliceHeader
少了一个字段
func byteString(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
结构体与[]byte
同理,结构体与[]byte也是可以互相转换的,可以用于优化encoding/binady
的性能。
type MyStruct struct {
A int
B int
}
// 获取结构体真实数据的大小
var sizeOfMyStruct = int(unsafe.Sizeof(MyStruct{}))
// 填充[]byte的数据结构
// 结构体的数据指针也就是一个4字节的int类型(c基础知识!)
func MyStructToBytes(s *MyStruct) []byte {
var x reflect.SliceHeader
x.Len = sizeOfMyStruct
x.Cap = sizeOfMyStruct
x.Data = uintptr(unsafe.Pointer(s))
return *(*[]byte)(unsafe.Pointer(&x))
}
// unsafe.Pointer(&b):取[]byte首地址
// (*reflect.SliceHeader)(unsafe.Pointer(&b)) : 强制转换其为reflect.SliceHeader指针
// (*reflect.SliceHeader)(unsafe.Pointer(&b)).Data : 将slice的数据指针取出来
// unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&b)).Data) : 将uint指针转成任意指针
// (*MyStruct)(unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&b)).Data)):成功转换
func BytesToMyStruct(b []byte) *MyStruct {
return (*MyStruct)(unsafe.Pointer(
(*reflect.SliceHeader)(unsafe.Pointer(&b)).Data))
}
写在后头
上头写的东西,感觉有些突破语言既定的规范了,当然我这么想是有根据的,从源码注释上来看,显然,这个语言特性是不稳定的,指不定哪天Go开发人员觉着这个黑魔法
不够魔性,直接不允许你这么做了。
黑魔法
,也是被一些人诟病的地方吧,有好有坏,不了解也可以,了解了也不是不可。
注意:使用这种方式去转换,是无法对数据进行修改的。意思是,调用了ToBytes
后得到的[]byte
是没有办法改变的,一旦修改即会出现unexpected fault address xxxxx
,只可读,而没有写操作的能力,切记切记,。
// ToString unsafe 转换, 将 []byte 转换为 string
func ToString(p []byte) string {
return *(*string)(unsafe.Pointer(&p))
}
// ToBytes unsafe 转换, 将 string 转换为 []byte
func ToBytes(str string) []byte {
return *(*[]byte)(unsafe.Pointer(&str))
}
// IntToBool int 类型转换为 bool
func IntToBool(i int) bool {
return i != 0
}
// SliceInt64ToString []int64 转换为 []string
func SliceInt64ToString(si []int64) (ss []string) {
ss = make([]string, 0, len(si))
for k := range si {
ss = append(ss, strconv.FormatInt(si[k], 10))
}
return ss
}
// SliceStringToInt64 []string 转换为 []int64
func SliceStringToInt64(ss []string) (si []int64) {
si = make([]int64, 0, len(ss))
var (
i int64
err error
)
for k := range ss {
i, err = strconv.ParseInt(ss[k], 10, 64)
if err != nil {
continue
}
si = append(si, i)
}
return
}
// SliceStringToInt []string 转换为 []int
func SliceStringToInt(ss []string) (si []int) {
si = make([]int, 0, len(ss))
var (
i int
err error
)
for k := range ss {
i, err = strconv.Atoi(ss[k])
if err != nil {
continue
}
si = append(si, i)
}
return
}
// MustInt 将string转换为int, 忽略错误
func MustInt(s string) (n int) {
n, _ = strconv.Atoi(s)
return
}
// MustInt64 将string转换为int64, 忽略错误
func MustInt64(s string) (i int64) {
i, _ = strconv.ParseInt(s, 10, 64)
return
}