Golang map
map是什么?
map就是一个kv键值对的集合,可以根据key在o(1)的时间复杂度内取到value。在Golang中map的底层实现,是使用的类似拉链法的方法解决hash冲突的。
什么是hash冲突?
hash表的原理是将多个kv键值对散列的存储到buckets中,buckets是一个连续的数组。存储kv值需要计算hash值和计算索引位置。
1.计算hash值。根据hash函数将key转化为一个hash值。
2.计算索引位置。利用hash值对,桶的数量,取模得到一个索引值,这样就找到了位置。
不妨思考一下,如果我们得到的hash值相同,那么计算得到的hash位置必定相同,这就造成了哈希冲突,这个需要怎么解决?
1.拉链法。
2.开放寻址法。
拉链法
拉链法是一种常见的解决哈希冲突的方法,拉链法主要实现是底层不直接使用连续数组来直接存储数据元素,而是通过数组和链表组合使用,数组里存储一个指针,指向一个链表。如果链表过长,也可以使用优化策略,比如用红黑树代替链表。
开放寻址法
开发地址法与拉链法不同,开发地址法是将具体的数据元素存储在数组桶中,在要插入新元素是,先根据哈希函数算出哈希值,根据哈希值计算索引,如果发现冲突了,就从计算出的索引位置向后探测,直到找到未使用的数据槽为止。
map的底层实现原理?
map的底层实现数据结构实际上就是一个hash表。在运行时表现为指向hmap结构的指针,hmap中记录了桶数组指针,溢出桶指针以及元素个数等字段。每个桶是一个bmap的数据结构体,可以存储8个kv和8个tophash以及指向下一个溢出桶的指针。为了内存紧凑,采用的是先存8个key后再存value。
map的内存模型
表示map的其实就是hmap结构体:
type hmap struct {
count int // # 返回的就是 len(map)
flags uint8
B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
noverflow uint16 // 溢出桶的bucket近似数
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
}
B 确定了hmap有2^B次方个桶,每个桶里面都是一个bmap,bmap在编译期间是动态创建的一个新结构:
type bmap struct {
topbits [8]uint8
keys [8]keytype
values [8]valuetype
pad uintptr
overflow uintptr
}
所以,bmap就是我们说的桶。一个桶里面最多可以装8个kv键值对,这些key之所以会在一个桶内,是因为他们经过哈希计算之后,哈希结果是’一类’的。在桶内,会根据hash值高8位决定key放到那个桶内的某个位置上(一个桶8个位置)。
在hmap中有一个extra *mapetra的字段,这个字段的作用是为了防止GC扫描时把溢出桶也处理了,保证GC的扫描性能。
为什么呢?这主要与map的类型有关,如果map的kv是值类型,那么就不用考虑GC扫描,如果是指针类型或着是需要GC扫描的类型,都需要放在extra里面,防止被扫描。
具体的map底层原理图:
对于map查找kv键值对的时候,我是这样理解的:map 底层是一个 hmap 结构,hmap 包含一个 buckets 指针,它指向一个由多个 bmap 组成的数组。哈希值的低 B 位决定使用哪个 bmap,然后根据高位哈希值在该 bmap 内查找具体的键值对。
map赋值原理
1.在对map进行赋值操作的时候,map一定要先进行初始化,否则panic。
var m map[int]int
m[1]=1
2.map不是线程安全的,不支持并发读写操作。
func main() {
m := map