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
}
网友评论