官方文档 : golang.org/pkg/unsafe/
简单介绍
unsafe很简单就是不安全,Java中也有(我原来写的一篇文章:anthony-dong.gitee.io/post/java-u…),而且使用Unsafe类可以做很多操作。
unsafe 其实就是拿到了数据真实的内存地址,你可以直接操作内存,脱离于语言本身的一些约束,但是由于直接操作内存太过于直接,暴力,所以对于有些时候操作和访问会很方便和快,也就是不走编译器的自己的内部逻辑,因为go本身是一个内存操作安全的语言
其中对于unsafe ,golang抽象的特别好,就几个方法核心的概念。
Pointer 和 uintptr
Pointer represents a pointer to an arbitrary type. There are four special operations available for type Pointer that are not available for other types:
- A pointer value of any type can be converted to a Pointer.
- A Pointer can be converted to a pointer value of any type.
- A uintptr can be converted to a Pointer.
- A Pointer can be converted to a uintptr.
官方给的定义,指针可以指向任意类型的指针,指针类型有四种特殊操作,而其他类型没有的,
-任何类型的指针值都可以转换为Pointer。
-Pointer可以转换为任何类型的指针值。
-uintptr可以转换为Pointer。
-Pointer可以转换为uintptr。
- 含义其实就是,unsafe.Pointer是一个指针(指向一块内存区域),uintptr是一个整型,它的值是一个内存地址(就是一个数字),所以它不是引用,属于直接开辟的内存,所以时时刻刻可能被垃圾回收(go语言的内存地址会变化,指的是分配在栈上的内存,堆上是不会变化得,栈上初始是2k,当要扩容的时候是先开辟一块大的,然后再将2k的copy过去,使用这块大的,然后gc最后回收掉这个内存, 关于开辟在堆、栈上是程序开始的时候就确定的,会通过逃逸分析进行确定,也就是有些对象看似在栈上分配,实际上却去了堆上了,属于一种策略)。
type T struct {
x int
y *[1 << 10]byte // 1 << 10=1024 1k,
}
func main() {
wg:=sync.WaitGroup{}
wg.Add(1)
go func() {
t := T{y: new([1 << 10]byte)}
addr := uintptr(unsafe.Pointer(&t.y[0]))
set(addr)
fmt.Println(t.y[0]) // 1
wg.Done()
}()
wg.Wait()
}
func set(addr uintptr) {
*(*byte)(unsafe.Pointer(addr)) = 1
}
这个没问题
func main() {
wg:=sync.WaitGroup{}
wg.Add(1)
go func() {
t := T{y: new([1 << 10]byte)}
addr := uintptr(unsafe.Pointer(&t.y[0]))
time.Sleep(time.Second) //加一个添加剂,让gc回收一下,
set(addr)
fmt.Println(t.y[0]) // 0
wg.Done()
}()
wg.Wait()
}
- 前两句大概可以用如下实现,下面很简单是将一个 大的类型-> 小的类型 (a->b)必须要求a本身的内存大于b
func convert(f uint64) uint32 {
// 任何指针类型可以转换为 Poniter
pointer := unsafe.Pointer(&f)
// Pointer可以转换为任何类型的指针值
return *(*uint32)(pointer)
}
- 其次就是后面两句,互相转换
type Foo struct {
Name string
}
func setName(f *Foo) {
_type := reflect.TypeOf(*f)
field, _ := _type.FieldByName("Name")
// uintptr类型
u := field.Offset
// Pointer类型
pointer := unsafe.Pointer(f)
// uintptr(pointer) Pointer类型可以转换为uintptr, 同时unsafe.Pointer(uintptr(pointer) + u)可以发现uintptr可以转换为Pointer
name := (*string)(unsafe.Pointer(uintptr(pointer) + u))
*name = "小李"
fmt.Printf("%+v\n", *f)
}
func main() {
setName(&Foo{})
}
以上就是两者的介绍,大致可以发现 Poniter的参数必须是指针(不能是空指针nil),同时Pointer可以转换为任意类型的其他指针。
- 注意reflect包中的Pointer方法返回的是uintptr
u := reflect.ValueOf(new(int)).Pointer()
p := (*int)(unsafe.Pointer(u))
*p = 1000
fmt.Println(*p)
- 其中还有一个特别好用的工具是,因为我们知道string底层本质上是 len+byte数组 , 而 切片本质上是 len+byte数组+容量,所以类型是 slice 到 string 没问题, 但是反过来会丢失 cap信息。
func convertStringToSlice() {
str := "123456"
var arr = *(*[]byte)(unsafe.Pointer(&str))
fmt.Println(len(arr))// 6
fmt.Println(cap(arr)) // 824634171480 ,这个数字是随机的,根据挨着的内存决定
}
func convertSliceToString() {
slice := make([]byte,100)
str := *(*string)(unsafe.Pointer(&slice))
fmt.Println(len(str)) // 100
}
如何解决上诉的问题:
type String struct {
data string
cap int
}
func convertStringToSlice() {
str := String{"123456", 6}
//str := "123456"
var arr = *(*[]byte)(unsafe.Pointer(&str))
fmt.Println(len(arr)) // 6
fmt.Println(cap(arr)) // 6
}
- 利用内存一致来转换一些对象
type UserModel struct { // 数据库对象
Name string
Age int
Birthday time.Time
}
type UserDto struct { // 数据传输对象,还要求数据传出的格式是 标准格式
Name string `json:"name"`
Age int `json:"age"`
Birthday Time `json:"birthday"`
}
type Time time.Time
func (this Time) MarshalText() (text []byte, err error) {
i := time.Time(this)
return []byte(i.Format("2006-01-02 15:04:05")), nil
}
如果手动的操作的话,需要一个个赋值, 很麻烦,还开辟新的内存,但是使用这个。
func BenchmarkName(b *testing.B) {
for i := 0; i < b.N; i++ {
model := UserModel{
Name: "tom",
Age: 1,
Birthday: time.Now(),
}
userDao := (*UserDto)(unsafe.Pointer(&model))
_, _ = json.Marshal(userDao)
//fmt.Println(string(bytes))
//// {"name":"tom","age":1,"birthday":"2020-06-26 15:20:20"}
}
}
- 关于string的一些细节底层优化
// slicebytetostringtmp returns a "string" referring to the actual []byte bytes.
//
// Callers need to ensure that the returned string will not be used after
// the calling goroutine modifies the original slice or synchronizes with
// another goroutine.
//
// The function is only called when instrumenting
// and otherwise intrinsified by the compiler.
//
// Some internal compiler optimizations use this function.
// - Used for m[T1{... Tn{..., string(k), ...} ...}] and m[string(k)]
// where k is []byte, T1 to Tn is a nesting of struct and array literals.//
// - Used for "<"+string(b)+">" concatenation where b is []byte. // 字符串比较
// - Used for string(b)=="foo" comparison where b is []byte. // 字符串比较
func slicebytetostringtmp(b []byte) string {
if raceenabled && len(b) > 0 {
racereadrangepc(unsafe.Pointer(&b[0]),
uintptr(len(b)),
getcallerpc(),
funcPC(slicebytetostringtmp))
}
if msanenabled && len(b) > 0 {
msanread(unsafe.Pointer(&b[0]), uintptr(len(b)))
}
return *(*string)(unsafe.Pointer(&b))
}
unsafe.Offsetof(structValue.field)
这个主要是服务于结构体的,计算算结构体字段的偏移量的,和Java的操作类似
func TestDemo(t *testing.T) {
model := UserModel{}
modelAdd := uintptr(unsafe.Pointer(&model))
nameAdd := unsafe.Offsetof(model.Name)
*(*string)(unsafe.Pointer(modelAdd + nameAdd)) = "tom"
ageAdd := unsafe.Offsetof(model.Age)
*(*int)(unsafe.Pointer(modelAdd + ageAdd)) = 10
birthAdd := unsafe.Offsetof(model.Birthday)
*(*Time)(unsafe.Pointer(modelAdd + birthAdd)) = Time(time.Now())
fmt.Println(model)
fmt.Printf("name_offset=%d, age_offset=%d, birethAdd=%d\n", nameAdd, ageAdd, birthAdd)
}
// {tom 10 2020-06-26 15:42:02.909539 +0800 CST m=+0.000558247}
// name_offset=0, age_offset=16, birethAdd=24
// string 是16个字节
// int 是 8个字节
//
unsafe.Sizeof()
计算一个对象的大小,指针、结构体、基本数据类型 , 单位是字节 。参数随意了,指针、直接引用都可以
结构体 :
func main() {
fmt.Println(unsafe.Sizeof(&time.Time{}))
}
// 指针是8个字节
考虑到可移植性,引用类型或包含引用类型的大小在32位平台上是4个字节,在64位平台上是8个字节。
| 类型 | 大小 |
|---|---|
bool |
1个字节 |
intN, uintN, floatN, complexN |
N/8个字节(例如float64是8个字节) |
int, uint, uintptr |
1个机器字 |
*T |
1个机器字 |
string |
2个机器字(data,len) |
[]T |
3个机器字(data,len,cap) |
map |
1个机器字 |
func |
1个机器字 |
chan |
1个机器字 |
interface |
2个机器字(type,value) |
unsafe.Alignof()
返回对应参数的类型需要对齐的倍数. 和 Sizeof 类似, Alignof 也是返回一个常量表达式, 对应一个常量. 通常情况下布尔和数字类型需要对齐到它们本身的大小(最多8个字节), 其它的类型对齐到机器字大小.
可以理解为他是 sizeof/8 ,