Go map |青训营笔记

73 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第5天

map

map表的底层原理是哈希表,其结构体定义如下:

 type Map struct {
     Key  *Type // Key type
     Elem *Type // Val (elem) type
 ​
     Bucket *Type // 哈希桶
     Hmap   *Type // 底层使用的哈希表元信息
     Hiter  *Type // 用于遍历哈希表的迭代器
 }

其中的Hmap 的具体化数据结构如下:

 type hmap struct {
     // Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
     // Make sure this stays in sync with the compiler's definition.
     count     int // map目前的元素数目
     flags     uint8 // map状态(正在被遍历/正在被写入)
     B         uint8  // 哈希桶数目以2为底的对数(哈希桶的数目都是 2 的整数次幂,用位运算来计算取余运算的值, 即 N mod M = N & (M-1)))
     noverflow uint16 //溢出桶的数目, 这个数值不是恒定精确的, 当其 B>=16 时为近似值
     hash0     uint32 // 随机哈希种子
 ​
     buckets    unsafe.Pointer // 指向当前哈希桶的指针
     oldbuckets unsafe.Pointer // 扩容时指向旧桶的指针
     nevacuate  uintptr        // 桶进行调整时指示的搬迁进度
 ​
     extra *mapextra // 表征溢出桶的变量
 }

以上Hmap基本都是涉及到了哈希桶和溢出桶,我们首先看一下它的数据结构,如下:

 type bmap struct {
     topbits  [8]uint8    // 键哈希值的高8位
     keys     [8]keytype  // 哈希桶中所有键
     elems    [8]elemtype    // 哈希桶中所有值
     //pad      uintptr(新的 go 版本已经移除了该字段, 我未具体了解此处的 change detail, 之前设置该字段是为了在 nacl/amd64p32 上的内存对齐)
     overflow uintptr
 }

我们会发现哈希桶bmap一般指定其能保存8个键值对,如果多于8个键值对,就会申请新的buckets,并将其于之前的buckets链接在一起。

在具体插入时,首先会根据key值采用相应的hash算法计算对应的哈希值,将哈希值的低8位作为Hmap结构体中buckets数组的索引,找到key值所对应的bucket,将哈希值的高8位催出在bucket的tophash中。

特点如下:

  1. map是无序的(原因为无序写入以及扩容导致的元素顺序发生变化),每次打印出来的map都会不一样,它不能通过index获取,而必须通过key获取
  2. map的长度是不固定的,也就是和slice一样,也是一种引用类型
  3. 内置的len函数同样适用于map,返回map拥有的key的数量
  4. map的key可以是所有可比较的类型,如布尔型、整数型、浮点型、复杂型、字符串型……也可以键。

如下方式即可进行初始化:

 var a map[keytype]valuetype
 //a map表名字
 //keytype   键类型
 //valuetype 键对应的值的类型
 ​
 //除此以外还可以使用make进行初始化,代码如下:
 var m map[string]int = map[string]int{"hunter":12,"tony":10}
 ​
 //我们还可以使用初始值进行初始化,如下:
 var m map[string]int = map[string]int{"hunter":12,"tony":10}
 ​
 ​

插入数据

map的数据插入代码如下:

 map_variable["mars"] = 27

插入过程如下:

  1. 根据key值计算出哈希值
  2. 取哈希值低位和hmap.B取模确定bucket位置
  3. 查找该key是否已经存在,如果存在则直接更新值
  4. 如果没有找到key,则将这一对key-value插入

删除数据

delete(map, key) 函数用于删除集合的元素, 参数为 map 和其对应的 key。删除函数不返回任何值。相关代码如下:

    countryCapitalMap := map[string] string {"France":"Paris","Italy":"Rome","Japan":"Tokyo","India":"New Delhi"}
    /* 删除元素 */
    delete(countryCapitalMap,"France");
 ​

查找数据

通过key获取map中对应的value值。语法为:map[key] .但是当key如果不存在的时候,我们会得到该value值类型的默认值,比如string类型得到空字符串,int类型得到0。但是程序不会报错。

所以我们可以使用ok-idiom获取值,如下:value, ok := map[key] ,其中的value是返回值,ok是一个bool值,可知道key/value是否存在。

在map表中的查找过程如下:

  1. 查找或者操作map时,首先key经过hash函数生成hash值
  2. 通过哈希值的低8位来判断当前数据属于哪个桶
  3. 找到桶之后,通过哈希值的高八位与bucket存储的高位哈希值循环比对
  4. 如果相同就比较刚才找到的底层数组的key值,如果key相同,取出value
  5. 如果高八位hash值在此bucket没有,或者有,但是key不相同,就去链表中下一个溢出bucket中查找,直到找到链表的末尾
  6. 如果查找不到,也不会返回空值,而是返回相应类型的0值。