Go中的指針有兩種示启,一種是內(nèi)置類型uintptr湃密,本質(zhì)是一個整型巫员,另一種是unsafe包提供的Pointer蛾坯,表示可以指向任意類型的指針。通常uintptr用來進行指針計算疏遏,因為它是整型脉课,所以很容易計算出下一個指針所指向的位置,而unsafe.Pointer用來進行橋接财异,用于不同類型的指針進行互相轉(zhuǎn)換倘零。
Go中也提供了unsafe.Pointer和uintptr使用的一些準則。
有了這些基本概念戳寸,我們可以怎么玩呢呈驶?
通常我們將byte[]轉(zhuǎn)換成string是這樣做的:
b := byte[]("Peppa")
string(b)
這個方式有個問題,就是會分配一份內(nèi)存并進行拷貝疫鹊,更高效的方式應(yīng)該是不分配任何內(nèi)存袖瞻,在原有內(nèi)存上進行類型轉(zhuǎn)換。
slice 本質(zhì)是一個結(jié)構(gòu)體拆吆,里面含有指向底層數(shù)組的指針以及Len聋迎、Cap成員,通過reflect包可以看到slice是這樣表示的:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
其中Data是指向底層數(shù)組第一個元素的指針枣耀。byte slice之所以能在原有內(nèi)存上轉(zhuǎn)換成string是因為string結(jié)構(gòu)和slice比較像霉晕,通過reflect包可以看到string是這樣表示的:
type StringHeader struct {
Data uintptr
Len int
}
所以我們只要在原始內(nèi)存上構(gòu)造出來StringHeader就可以了。
sh := reflect.StringHeader{
uintptr(unsafe.Pointer(&b[0])),
len(b),
}
其中Data指向的是slice的第一個元素捞奕,Len是slice的長度牺堰。這里使用unsafe.Pointer進行了橋接。
創(chuàng)建好StringHeader對象颅围,下一步就是將其轉(zhuǎn)換成string類型的對象伟葫,如何做呢?還是通過unsafe.Pointer進行橋接院促。
*(*string)(unsafe.Pointer(&sh))
先將StringHeader類型的指針轉(zhuǎn)換到unsafe.Pointer筏养,然后再將unsafe.Pointer轉(zhuǎn)換成string類型的指針斧抱,最后通過指針取值獲得實際的值。
這種通過unsafe.Pointer進行類型轉(zhuǎn)換的方式在unsafe包中也進行了說明撼玄,其中舉的例子就是math包中的Float64bits方法:
func Float64bits(f float64) uint64 {
return *(*uint64)(unsafe.Pointer(&f))
}
我們可以驗證兩種方式是否重新分配了內(nèi)存夺姑。
import (
"github.com/davecgh/go-spew/spew"
"unsafe"
"reflect"
"fmt"
)
func byteToString(b []byte) string {
return string(b)
}
func byteToStringNoAlloc(b []byte) string {
if len(v) == 0 {return ""}
sh := reflect.StringHeader{uintptr(unsafe.Pointer(&b[0])), len(b)}
return *(*string)(unsafe.Pointer(&sh))
}
func main() {
b := []byte("Peppa")
fmt.Println("切片第一個元素: ", spew.Sdump(&b[0]))
str := byteToString(b)
sh := (*reflect.StringHeader)(unsafe.Pointer(&str))
fmt.Println("分配內(nèi)存的方式: ", spew.Sdump(sh))
strNoAlloc := byteToStringNoAlloc(b)
shNoAlloc := (*reflect.StringHeader)(unsafe.Pointer(&toStr))
fmt.Println("不分配內(nèi)存的方式: ", spew.Sdump(shNoAlloc))
}
這種方式會帶來一個比較嚴重的問題墩邀,Data指向的內(nèi)存可能隨時會被回收掌猛,因為uintptr是不安全的,所以當那段內(nèi)存被回收后眉睹,這種在原始內(nèi)存上進行轉(zhuǎn)換的操作可能會導致panic荔茬,所以在使用這種方式時要保持警惕。
unsafe包也提供了安全的stringHeader和sliceHeader竹海,不過它們沒有暴露出來慕蔚,所以暫時還沒有辦法避免內(nèi)存不被回收的類型轉(zhuǎn)換。