在Go语言中提供了map数据结构来存储键值对数据。map的数据类型为map[K]V,其中K为键的类型,V为值的类型。map的键类型必须支持==操作符,用来比较两个键是否相等。Go语言提供了4种内置的map操
在Go语言中提供了map数据结构来存储键值对数据。map的数据类型为map[K]V,其中K为键的类型,V为值的类型。map的键类型必须支持==操作符,用来比较两个键是否相等。Go语言提供了4种内置的map操作: len、delete、comparison、assign。 Map 定义
Map IterationGo语言提供了两个方法来遍历map中的所有键值对,分别是range方法和Len()方法。
Map 的线程安全在Go语言中,map是非线程安全的,在多线程并发访问时可能导致程序报错。当map被多个协程同时访问时,我们需要使用sync包中的sync.Mutex来确保操作的原子性和并发安全。
map 底层原理Go语言的map在设计上是一种哈希表的数据结构。它利用哈希函数将键映射到不同的存储空间,从而实现高效的查找和插入操作。 哈希函数哈希函数将字符串映射到一个整数上,这称为哈希值。不同的字符串可能会有相同的哈希值,但相同的字符串必定具有相同的哈希值。哈希函数需要满足两点:
在Go语言中,字符串的哈希函数采用的是FNV-1哈希算法,算法代码如下:
哈希冲突在哈希表中,哈希值相同的多个字符串可能会存储在同一个位置上,这种现象叫做哈希冲突。哈希冲突处理策略有开放寻址法、再哈希法和链地址法。
Go语言使用链地址法处理哈希冲突。对于每个存储单元,map结构体中还维护了一个[]keyValue类型的链表。
Growing在Go语言中,动态数组会自动地为map分配更多的空间。Growing过程涉及到将原始的数组重新复制到一个更大的数组中,其中原数组的元素需要重新计算其在新数组中的位置,而新数组的元素则需要将其键值对填充到相应的位置。Growing的过程比较复杂,可以由函数hashGrow()来控制。
map扩容双倍扩容Go语言中的哈希表在map的数组容量达到一定程度时,就会自动进行扩容。扩容的依据是当前已存储的元素数量和数组的长度之间的比值:
Go语言的map会优先选择数组大小为原数组大小的2倍,以确保map在存储过程中有足够的空间存放新的元素。当元素数量达到85%时,Go语言就会再次对数组进行扩容,此时数组长度翻倍,以保证数组长度和元素数量的比例始终维持在0.75左右,以平衡效率和空间占用。 Growing过程当映射中的元素数量超过85%时,Go语言就会触发map的扩容过程。在扩容的过程中,map会将原有的元素复制到新的数组中,并将新数组的初始大小设置为原数组的2倍。对于发生哈希冲突的元素,需要在新的数组中重新计算哈希地址。 避免溢出当数组中元素的数量超过0x7fffffff(2^31-1,即int类型的最大值)时,就会发生溢出,此时数组的大小将无法达到原数组的2倍。所以Go语言会在初始创建map时,为其初始化一个较小的数组,并设置map的B值,以便在元素数量超过限制时再次进行扩容。当map中元素的数量超过阈值时,会再次翻倍,直到数组大小小于0x7fffffff为止。 代码分析hashmap.go包含在Go语言源码中的src/container/map.go文件中。其中map结构体的定义和Growing实现都在runtime包中,在src/runtime/map.go文件中。 附录
|
2022-04-28
2022-04-21
2022-05-13
2022-08-17
2024-05-07