nil切片和空切片
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
var s1 []int // nil切片
s2 := make([]int,0) // 空切片
s4 := make([]int,0) // 空切片
// (*reflect.SliceHeader)(unsafe.Pointer(&s1)) 将s1,s2,s3 都转换为reflect.SliceHeader类型
fmt.Printf("s1 pointer:%+v, s2 pointer:%+v, s4 pointer:%+v, \n",
*(*reflect.SliceHeader)(unsafe.Pointer(&s1)),
*(*reflect.SliceHeader)(unsafe.Pointer(&s2)),
*(*reflect.SliceHeader)(unsafe.Pointer(&s4)))
fmt.Printf("%v\n",
(*(*reflect.SliceHeader)(unsafe.Pointer(&s1))).Data==
(*(*reflect.SliceHeader)(unsafe.Pointer(&s2))).Data)
fmt.Printf("%v\n",
(*(*reflect.SliceHeader)(unsafe.Pointer(&s2))).Data==
(*(*reflect.SliceHeader)(unsafe.Pointer(&s4))).Data)
}
reflect.SliceHeaderGo语言slice的本质-SliceHeader_飞雪无情的博客-CSDN博客
nil切片和空切片最大的区别在于指向的数组引用地址是不一样的。
所有的空切片指向的数组引用地址都是一样的
三种指针类型
*T:普通类型指针类型,用于传递对象地址,不能进行指针运算。
unsafe.poniter:通用指针类型,用于转换不同类型的指针,不能进行指针运算,不能读取内存存储的值(需转换到某一类型的普通指针)
uintptr:用于指针运算,GC不把uintptr当指针,uintptr无法持有对象。uintptr类型的目标会被回收。
uintptr和unsafe.Pointer的区别
unsafe.Pointer 可以和 普通指针 进行相互转换;
unsafe.Pointer 可以和 uintptr 进行相互转换。
import (
"fmt"
"unsafe"
)
type W struct{
b int32
c int64
}
func main(){
var w *W = new(W)
// 这时w的变量打印出来的都是默认值0,0
fmt.Println(w.b,w.c)
// 下面走啊网盘,二命通过指针运算给b变量赋值为10
b := unsafe.Pointer(uintptr(unsafe.Pointer(w)) + unsafe.Offsetof(w,b))*((*int)(b)) == 10
// 此时结果就变成了10,0
fmt.Println(w.b,w.c)
}
uintptr(unsafe.Pointer(w))获取了w指针的起始值
unsafe.Offsetof(w.b)获取了b变量的偏移量
两个相加就得到了b的地址值,将通用指针pointer转换成具体指针((*int)(b)),通过 * 符取值,然后赋值 ` * (( *int)(b))相当于把 ( *int)(b) 转换成 int了,最后对变量重新赋值成10,这样指针运算就完成了
Switch type
如果switch type的case后面只有一个类型T1,那么msg对应的类型就是这个类型T1。 如果switch type的case后面有多个类型(T2,T3),那么msg对应的类型就是interface。
type base interface{ F() }
type student struct{ Name string }
func (s *student) F() {}
type class struct{ Name string }
func (c *class) F() {}
type teacher struct{ Name string }
func (t *teacher) F() {}
func isType(v interface{}) {
switch msg := v.(type) {
case student, teacher:
fmt.Println(msg.Name) //这里会报错,因为这个msg是interface类型,没有Name属性
case class:
fmt.Println(msg.Name) //这里不会报错,因为这个msg是class类型,有Name属性
}
}
Map的实现原理
map是一种key-value键值对的存储结构,其中key是不能重复的,其底层实现采用的是hash表。
当我们用dataMap := make(map[int]string)创建一个 map 对象的时候,得到的是一个 hmap 指针结构。
type hmap struct {
count int // 当前的元素个数
flags uint8
B uint8 // 字段 buckets 数组的长度关联参数,即 buckets 长度为 2^B
noverflow uint16 // overflow buckets 的近似数
hash0 uint32 // 哈希种子,作 hash 运算用的
buckets unsafe.Pointer // 数组对象,存 key-value的
oldbuckets unsafe.Pointer // 扩容时用到的 buckets,大小是之前 buckets 的一半。
nevacuate uintptr // 扩容进度
extra *mapextra // optional fields
}
在这里面,有一个非常关键的字段:buckets,它就是用来存储 key-value 的。
当 Go 对 key 进行 hash 运算后,会通过一定的规则映射到buckets的某个位置,然后就会将 key、value 一起存在这个对应位置的桶里。
桶就会指向这个结构体
type bmap struct {
tophash [bucketCnt]uint8
}
之所以这么简洁,是因为map 是在编译时才知道对应的 key-value 类型的,所以map在编译过后bmap就会变成
type bmap struct {
tophash [bucketCnt]uint8
keys [bucketCnt]string
values [bucketCnt]int
overflow *bmap
}
在这里,就有一个 keys,values 数组来存储 key-value 了,其中还有个 tophash 数组,可以认为它是用来定位 key-value 在对应数组里的存储位置