• Go语言Map详解


    map(字典、哈希表、映射)是一种使用频率很高的数据结构,将其作为语言的内置类型,从运行时层面进行优化,可获得更好的性能。

    一、内部实现

    map的源码结构为:

    // A header for a Go map.
    type hmap struct {
        // Note: the format of the Hmap is encoded in ../../cmd/internal/gc/reflect.go and
        // ../reflect/type.go. Don't change this structure without also changing that code!
        count     int // # live cells == size of map.  Must be first (used by len() builtin)
        flags     uint8
        B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
        noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
        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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    buckets和一个oldbuckets是用来实现增量扩容的。正常情况下直接使用buckets,而oldbuckets为空。如果当前哈希表正在扩容中,则oldbuckets不为空,并且buckets大小是oldbuckets大小的两倍。

    type mapextra struct {
        // If both key and value do not contain pointers and are inline, then we mark bucket
        // type as containing no pointers. This avoids scanning such maps.
        // However, bmap.overflow is a pointer. In order to keep overflow buckets
        // alive, we store pointers to all overflow buckets in hmap.overflow.
        // Overflow is used only if key and value do not contain pointers.
        // overflow[0] contains overflow buckets for hmap.buckets.
        // overflow[1] contains overflow buckets for hmap.oldbuckets.
        // The indirection allows to store a pointer to the slice in hiter.
        overflow [2]*[]*bmap
    
        // nextOverflow holds a pointer to a free overflow bucket.
        nextOverflow *bmap
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    hashmap 通过一个 bucket 数组实现,所有元素将被 hash 到数组中的 bucket 中,bucket 的8个 key/value 空间如果都填满后,就会分配新的 bucket,通过 overflow 指针串联起来。nextOverflow应用在当空间不够时(但是又不足以触发 rehash 逻辑)从系统中申请内存临时使用的空间做缓冲。

    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
        // Followed by bucketCnt keys and then bucketCnt values.
        // NOTE: packing all the keys together and then all the values together makes the
        // code a bit more complicated than alternating key/value/key/value/... but it allows
        // us to eliminate padding which would be needed for, e.g., map[int64]int8.
        // Followed by an overflow pointer.
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    bmap 是 bucket 数据结构的部分结构。 其功能是来大致确认这个链表的地址。是一个空间为 8 的数组。其值是 key 的 hash 值的高位。当传递进来一个 key 的时候,会做比对,然后确定这个数组下标,这个下标,与这个 key 所存储的链表头部有关。

    按key的类型采用相应的hash算法得到key的hash值。将hash值的低位当作Hmap结构体中buckets数组的index,找到key所在的bucket。将hash的高8位存储在了bucket的tophash中。注意,这里高8位不是用来当作key/value在bucket内部的offset的,而是作为一个主键,在查找时对tophash数组的每一项进行顺序匹配的。先比较hash值高位与bucket的tophash[i]是否相等,如果相等则再比较bucket的第i个的key与所给的key是否相等。如果相等,则返回其对应的value,反之,在overflow buckets中按照上述方法继续寻找。

    Bucket中key/value的放置顺序,是将keys放在一起,values放在一起,为什么不将key和对应的value放在一起呢?如果那么做,存储结构将变成key1/value1/key2/value2… 设想如果是这样的一个map[int64]int8,考虑到字节对齐,会浪费很多存储空间。

    二、创建和初始化

    1、使用make函数来创建并初始化map

    //创建一个映射,键的类型是string,值的类型是int
    dict:=make(map[string]int)
    
    • 1
    • 2

    当使用make创建map时,自动创建且初始化了map,此时map为空。

    2、使用映射字面量来创建并初始化map

    dict:=map[string]string{"Red:"#da1337","Orange":"#e95a22"}
    
    • 1

    使用映射字面量,map的初始长度会根据初始化时指定的键值对数量来确定。

    作为无序键值对集合,map要求key必须是支持相等运算符(==,!=)的数据类型,如:数字、字符串、指针、数组、结构体以及对应的接口;对于切片、函数、通道类型这类具有引用语义的不能作为map的key值。

    空映射与nil

    func main(){
        var m1 map[string]int
        m2:=map[string]int{}
    
        println(m1==nil,m2==nil)
    }
    输出:
         true false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    m1通过声明一个未初始化的map来创建一个值为nil的map,nil映射不能存储键值对,m1[“Red”]=1将会产生一个运行时错误。
    m2创建了一个空映射,内容为空,已经初始化,与make操作相同。

    三、使用映射

    测试map中key值是否存在

    测试一个map里面是否存在某个key值是map的一个重要操作,这个操作允许用户确定是否完成了某些操作或者是否map里缓存了特定的数据。
    1、可以同时取值以及一个表示key是否存在的标志。

    value,exists := colors["Blue"]
    if exists {
        fmt.Println("存在,value:",value)
    }
    
    • 1
    • 2
    • 3
    • 4

    2、当通过键值来索引,当key值不存在时也会返回一个类型对应的零值。

    value := colors["Blue"]
    if value!=""{
        fmt.Println("存在,value:",value)
    }
    
    • 1
    • 2
    • 3
    • 4

    map的遍历

    map是通过range来进行遍历,range返回的不是索引和值,而是键值对。

    for key,value :=range colors{
        fmt.Printf("key:%v,value:%v",key,value)
    }
    
    • 1
    • 2
    • 3

    如果想把key从一个map中删掉,直接使用内置的delete函数。

    delete(colors,"Red")
    
    • 1
  • 相关阅读:
    Python地理数据处理 十八:arcpy批量处理数据之栅格图像的统计分析
    回归预测 | MATLAB实现BO-BiLSTM贝叶斯优化双向长短期神经网络多输入单输出回归预测
    海量短视频打标问题之Active-Learning
    22年牛客最新大厂Java岗面试题大合集(含答案)
    Qt(day5)
    【2023】Git版本控制-本地仓库详解
    【STL】vector
    嵌入式数据库开发编程(四)——DDL、DML
    【yolov5】改进系列——特征图可视化(V7.0 的一个小bug)
    持续创作,还得靠它!
  • 原文地址:https://blog.csdn.net/Bejpse/article/details/126364280