go 高并发下的数据结构是怎样?

码农老张 后端 2024-07-21

go 高并发下的数据结构是怎样?

什么变量的大小是 0 字节

查看一个变量的字节大小go

代码解读
复制代码
fmt.Println(unsafe.Sizeof(int(0))) // 8
  • int 类型的变量大小是 8 字节,int 类型的变量大小是不固定的,会因为不同的操作系统而改变
  • int32 类型的变量大小是 4 字节
  • int64 类型的变量大小是 8 字节
  • 指针的字节长度是 8 字节,也会根据操作系统的不同而改变

空结构体的大小是 0 字节go

代码解读
复制代码
type Person struct {} func main() { p := Person{} fmt.Println(unsafe.Sizeof(p)) // 0 }

0 字节的变量在内存中的地址是相同的,称为 zerobase,这个变量在 runtime/malloc.go 文件中go

代码解读
复制代码
fmt.Printf("%p\n", &Person{}) // 0x1164340 -> 是 zerobase

不过要注意的是,空结构体如果在其他结构体(这个结构体中的成员要有非空结构体)中,那么这个结构体的地址就不是 zerobase 了go

代码解读
复制代码
type Person1 struct { p1 Person } p1 := Person1{} fmt.Printf("%p\n", &p1.p1) // 0x1164340 -> 是 zerobasego
代码解读
复制代码
type Person1 struct { p1 Person name string } p1 := Person1{} fmt.Printf("%p\n", &p1.p1) // 0xc000014070 -> 不是 zerobase

空结构体的主要作用就是为了节省内存,使用场景:hashsetchannel

字符串的底层

字符串在 go 中占用的是 16 字节,不管字符串的长度是多少,都是 16 字节go

代码解读
复制代码
fmt.Println(unsafe.Sizeof("a")) // 16 fmt.Println(unsafe.Sizeof("高并发下的数据结构")) // 16

字符串的底层是一个结构体 stringStruct,结构体中有两个字段,一个是指向字符串的指针,一个是字符串的长度

这个结构体在 runtime/string.go 文件中go

代码解读
复制代码
type stringStruct struct { str unsafe.Pointer len int }

但是这个结构体不能被外部直接访问

在反射包里面有一个 StringHeader 结构体,和 stringStruct 结构体是类似

这个结构体在 reflect/value.go 文件中go

代码解读
复制代码
type StringHeader struct { Data uintptr Len int }

StringHeader 中的 Len 字段是字符串的字节长度,这个长度和 len 方法返回的长度是一样的go

代码解读
复制代码
s := "高并发" fmt.Println((*reflect.StringHeader)(unsafe.Pointer(&s)).Len) // 9 len(s) // 9

字符串遍历应该使用 range 关键字,因为 range 关键字会自动处理 utf-8 编码的字符串go

代码解读
复制代码
s := "高并发" for _, char := range s { fmt.Println(string(char)) }

如果要切割字符串的话需要将字符串转成 rune 类型的切片go

代码解读
复制代码
s := "高并发" runeSlice := []rune(s) fmt.Println(string(runeSlice[0])) // 高

map 的底层

map 的底层是 hmap,在 runtime.map.go 文件中,结构如下:go

代码解读
复制代码
// A header for a Go map. type hmap struct { // Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go. // Make sure this stays in sync with the compiler's definition. count int // # live cells == size of map. Must be first (used by len() builtin) flags uint8 B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items) noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details hash0 uint32 // hash seed buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0. oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated) extra *mapextra // optional fields }
  • buckets:有这个属性的就是拉链法
  • Bbuckets 数量的 2 的对数
  • count:当前这个 hamp 中存储的key-value 的数量
  • hash0:哈希算法的种子
  • extra:溢出桶的信息

bucket 的结构如下:go

代码解读
复制代码
// A bucket for a Go map. type bmap struct { // tophash generally contains the top byte of the hash value // for each key in this bucket. If tophash[0] < minTopHash, // tophash[0] is a bucket evacuation state instead. tophash [bucketCnt]uint8 // Followed by bucketCnt keys and then bucketCnt elems. // NOTE: packing all the keys together and then all the elems together makes the // code a bit more complicated than alternating key/elem/key/elem/... but it allows // us to eliminate padding which would be needed for, e.g., map[int64]int8. // Followed by an overflow pointer. }
  • tophash:存储的是每一个 key 的前一个字节的哈希值
  • overflow:溢出指针go
代码解读
复制代码
func makemap(t *maptype, hint int, h *hmap) *hmap { mem, overflow := math.MulUintptr(uintptr(hint), t.Bucket.Size_) if overflow || mem > maxAlloc { hint = 0 } // initialize Hmap if h == nil { // new 了一个 hmap 结构体 h = new(hmap) } h.hash0 = fastrand() // Find the size parameter B which will hold the requested # of elements. // For hint < 0 overLoadFactor returns false since hint < bucketCnt. B := uint8(0) // 算出 B 的值 for overLoadFactor(hint, B) { B++ } h.B = B // allocate initial hash table // if B == 0, the buckets field is allocated lazily later (in mapassign) // If hint is large zeroing this memory could take a while. if h.B != 0 { var nextOverflow *bmap // 创建一些 bucket 和 overflow bucket h.buckets, nextOverflow = makeBucketArray(t, h.B, nil) if nextOverflow != nil { h.extra = new(mapextra) // overflow bucket 保存在 extra 中 h.extra.nextOverflow = nextOverflow } } return h }

map 初始结构:

go 高并发下的数据结构是怎样?

  1. hash 值通过 hash0key 计算出来
  2. tophashhash 值的高 8
  3. 放第几个 bucket,通过 B 来判断,B = 3 表示取 hash 值的低 3 位,然后把这 3 位换算成十进制

go 高并发下的数据结构是怎样?

转载来源:https://juejin.cn/post/7383983223239196713

Apipost 私有化火热进行中

评论