美文网首页
go map and slice 2021-10-08

go map and slice 2021-10-08

作者: 9_SooHyun | 来源:发表于2021-10-08 20:29 被阅读0次

go值传递

golang是值传递,什么情况下都是值传递

那么,如果结构中不含指针,则直接赋值就是深度拷贝;

如果结构中含有指针(包括自定义指针,以及slice,map等使用了指针的内置类型),则数据源和拷贝之间对应指针会共同指向同一块内存,这时深度拷贝需要特别处理。因为值传递只是把指针拷贝了

map

map源码:

https://github.com/golang/go/blob/a7acf9af07bdc288129fa5756768b41f312d05f4/src/runtime/map.go

map最重要的两个结构体:hmap 和 bmap

其中 hmap 充当了哈希表中数组的角色, bmap充当了链表的角色。

hmap

// A header for a Go map.

type hmap struct {
count     int // map内的元素个数,调用 len(map) 时,直接返回此值
flags     uint8 // 标志位,例如表示map正在被写入或者被遍历
B         uint8 // buckets 的对数 log_2,即含有 2^B 个buckets。这样的好处是方便用位操作实现取模
noverflow uint16 // 溢出桶的近似数
hash0     uint32 // 哈希种子
buckets   unsafe.Pointer // 【指向 buckets数组(连续内存空间),数组的类型为[]bmap,大小为 2^B】
oldbuckets unsafe.Pointer // 扩容的时候,buckets 长度会是 oldbuckets 的两倍
nevacuate uintptr // 指示扩容进度,小于此地址的 buckets 迁移完成
extra *mapextra // optional fields
}

bmap

其中,单个bucket是一个叫bmap的结构体.

Each bucket contains up to 8 key/elem pairs.

And the low-order bits of the hash are used to select a bucket. Each bucket contains a few high-order bits of each hash to distinguish the entries within a single bucket.

hash值的低位用来定位bucket,高位用来定位bucket内部的key

// 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 // 【bucketCnt在源码中被const为8, 每个bmap结构最多存放8组键值对】
// 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.
//
}

根据上面bmap的注释和https://github.com/golang/go/blob/go1.13.8/src/cmd/compile/internal/gc/reflect.go

我们可以推出bmap的结构实际是

type bmap struct {
  topbits [8]uint8 // 键哈希值的高8位
  keys     [8]keytype
  elems   [8]elemtype
  overflow uintptr // 当经由哈希函数映射到该地址的元素数超过 8 个时, 会将新的元素放到溢出桶中, 并使用 overflow 指针链向这个溢出桶
}

注意:在哈希桶中,键值之间并不是相邻排列的,而是键放在一起,值放在一起,来减少因为键值类型不同而产生的不必要的内存对齐

例如map[int64]int8,如果 key/elem/key/elem这样存放,那么int8类型的值就要padding 7个字节共56bits

更多可参考
https://zhuanlan.zhihu.com/p/406751292
https://studygolang.com/articles/32943

slice

// slice对应的结构体如下
// runtime/slice.go

type slice struct {
  array unsafe.Pointer // 元素指针
  len   int // 长度 
  cap   int // 容量
}

因此,slice、map作为参数传递给函数形参,在函数内部的改动会影响到原slice、map

package main

import "fmt"

func main() {
s := []int{1,2,3}
modifySlice(s)
fmt.Println(s) // [100 2 3]

m := map[int]int{1:1}
modifyMap(m)
fmt.Println(m) // map[1:1 100:100]
}

func modifySlice(s []int) {
s[0] = 100
}

func modifyMap(m map[int]int) {
m[100] = 100
}

相关文章

网友评论

      本文标题:go map and slice 2021-10-08

      本文链接:https://www.haomeiwen.com/subject/spzxoltx.html