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

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: **变量的字节大小**- `int`, `int32`, `int64` 分别为8, 4, 8字节;指针也为8字节,均受OS影响。- 空结构体大小为0字节,内存地址相同(`zerobase`),嵌入非空成员后地址不同。**字符串底层**- 占用16字节,无论长度。- 底层为`stringStruct`,含指向字符串的指针与长度。- `StringHeader`类比`stringStruct`用于反射。**map底层**- 基于`hmap`,含`buckets`、`B`、`count`等,用于散列与管理。- `bucket`含`tophash`和`overflow`

什么变量的大小是 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 -> 是 zerobase

go

代码解读

复制代码

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 初始结构:

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


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

相关文章
|
6月前
|
Go
《Go 简易速速上手小册》第3章:数据结构(2024 最新版)(下)
《Go 简易速速上手小册》第3章:数据结构(2024 最新版)(下)
32 1
|
6月前
|
存储 供应链 数据可视化
《Go 简易速速上手小册》第3章:数据结构(2024 最新版)(上)
《Go 简易速速上手小册》第3章:数据结构(2024 最新版)(上)
72 1
|
1月前
|
存储 Go 容器
深入探究Go语言中的数据结构
深入探究Go语言中的数据结构
41 3
|
5月前
|
安全 测试技术 Go
Go语言在高并发场景下的应用
在当今互联网高速发展的时代,高并发已成为众多应用系统面临的核心问题。本文探讨了Go语言在高并发场景下的优势,并通过具体实例展示了其在实际应用中的效果和性能表现。
|
1月前
|
Java Go 云计算
Go语言在云计算和高并发系统中的卓越表现
【10月更文挑战第10天】Go语言在云计算和高并发系统中的卓越表现
|
1月前
|
并行计算 算法 搜索推荐
探索Go语言的高并发编程与性能优化
【10月更文挑战第10天】探索Go语言的高并发编程与性能优化
|
3月前
|
安全 Java Go
探索Go语言在高并发环境中的优势
在当今的技术环境中,高并发处理能力成为评估编程语言性能的关键因素之一。Go语言(Golang),作为Google开发的一种编程语言,以其独特的并发处理模型和高效的性能赢得了广泛关注。本文将深入探讨Go语言在高并发环境中的优势,尤其是其goroutine和channel机制如何简化并发编程,提升系统的响应速度和稳定性。通过具体的案例分析和性能对比,本文揭示了Go语言在实际应用中的高效性,并为开发者在选择合适技术栈时提供参考。
|
3月前
|
搜索推荐 算法 Go
深入探索堆:Go语言中的高效数据结构
深入探索堆:Go语言中的高效数据结构
|
3月前
|
安全 Java Go
探索Go语言在高并发场景中的优势
Go语言,作为一种现代编程语言,凭借其并发模型和高效的性能,正在成为处理高并发任务的首选。本文深入探讨Go语言的并发特性,特别是goroutines和channels如何在实际应用中提供高效的解决方案。我们将通过对比其他语言的处理方式,展示Go语言在性能和可维护性上的优势。
|
5月前
|
并行计算 Go 数据处理
掌握Go语言:Go 并发编程,轻松应对大规模任务处理和高并发请求(34)
掌握Go语言:Go 并发编程,轻松应对大规模任务处理和高并发请求(34)