Golang 哈希表作为函数传参解惑
一、Golang所有变量的参数传递都是值传递
2、哈希表
这次我们讲哈希表作为函数参数的底层原理分析,首先我们看一下哈希表的结构体定义
在Golang中,哈希表是数组+单链表实现的,单链表节点保存了8个key-value和对应键的高八位,方便快速查找。这里简单作一下结构体中关键变量的含义,buckets是哈希表数据的指针变量,oldbuckets是旧哈希表数据的指针变量,保存新、旧哈希表指针变量的作用是为了哈希表渐进式扩容的。
我们来看一段代码,哈希表作为函数传递时,在函数内发生了扩容行为,主函数能否感受到哈希表的变化,然后为什么一定能感受到变化,而切片有些情况下不能?
func main() {
//m := make([]int, 0)
hashMap := make(map[int]int, 0)
//fmt.Printf("Map address1: %p\n", &m)
fmt.Printf("Slice address1: %p\n", &hashMap)
//test1(m)
testMap(hashMap)
fmt.Println(hashMap)
fmt.Printf("Slice address4: %p\n", &hashMap)
}
func testMap(m map[int]int) {
fmt.Printf("Map address3: %p\n", &m)
for i := 1; i < 2; i++ {
m[i] = i
}
fmt.Printf("Map address3: %p\n", &m)
}
输出结果为
Map address1: 0xc0000ac018//主函数参数的地址
Map address3: 0xc0000ac028//依然是行参
Map address3: 0xc0000ac028//行参扩容后
map[1:1]//主函数能感受到testMap函数对哈希表的添加行为
Map address4: 0xc0000ac018//主函数参数的地址
可以看到,Golang的参数传递都是值传递,在testMap函数中,哈希表发生了扩容行为,但是哈希表并不像切片一样,主函数的哈希表依然看到了添加行为。
这是为什么?
前面我们说了,buckets是哈希表数据的指针变量。形参m内的buckets是指针变量,指针作为参数传递的时候,其实是复制的地址,也就是原哈希表buckets的地址。
所以在形参上对哈希表的buckets添加元素,其实就是对原哈希表buckets进行添加元素。
不过你可能会疑惑,初始化时候哈希表大小是0,添加元素不是会发生扩容行为吗,发生了扩容行为,行参map的buckets应该是指向新的桶,和原map的buckets不是同一个啊,为什么原map还能看到。
这涉及到哈希表复杂的扩容触发机制和相应的扩容行为,扩容触发机制可以是装载因子或者是溢出桶数量的阈值。扩容行为是渐进式扩容的。渐进式扩容就是不马上把旧桶数据删掉,而是通过前面说的oldbuckets引用着先,慢慢把oldbuckets的数据引到新buckets中,
那为什么,行参map的buckets应该是指向新的桶,和原map的buckets不是同一个,为什么原map还能看到。
哈哈这个我也不知道,因为具体哈希表扩容行为我目前还不知道怎么控制(此处笑死)。