摘取自gateway,作者提到过这种方式,也是来源于另一个开源项目,操作方式极其硬核,故收藏了。

String与Slice互转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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语言的规矩,算是一种黑魔法

1
2
3
string的本质:reflect.StringHeader{}
slice的本质:reflect.SliceHeader{}
go指针的本质:unsafe.Pointer{}、uintptr{}

查看源码,可以找到Go编译器对slice与string的具体解释,均保留有一个指针,用于指向数据真正的地址。

1
2
3
4
5
6
7
8
9
10
11
12
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
SliceHeader是切片的运行时表示。它不能被安全或便携地使用,其表示可能会在以后的版本中更改。此外,数据字段不足以保证它引用的数据不会被垃圾收集,因此程序必须保留一个单独的,正确键入的指向底层数据的指针。

type StringHeader struct {
Data uintptr
Len int
}
StringHeader是字符串的运行时表示形式。

另外,我们看到这俩结构体类似,内存布局也是类似的,只是StringHeader少了一个字段,但至少内存是对其的,那么?(C语言又在发威了~)

注意:反过来转换不行,StringHeader对比SliceHeader少了一个字段

1
2
3
func byteString(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}

结构体与[]byte

同理,结构体与[]byte也是可以互相转换的,可以用于优化encoding/binady的性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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只可读,而没有写操作的能力,切记切记img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 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
}