• go map


    一、初始化

    var m1 map[int]bool  //未初始化,当前未nil,这里和slice有点不一样,map必须要初始化以后才能添加数据
    var m2 = make(map[int]bool)  //初始化
    var m3  = make(map[int]bool,5)  //初始化+容量  如果这里容量小于8其实没啥意义,因为一个bmap存储8个元素(1 << 3)
    var m4 = map[int]bool{  //初始化+赋值
        1:true,
        2:false,
    }
    
    //底层初始化的几个函数
    //func makemap_small() *hmap
    //func makemap64(t *maptype, hint int64, h *hmap) *hmap
    //func makemap(t *maptype, hint int, h *hmap) *hmap
    
    //makemap_small:当 hint 小于 8 时,会调用 makemap_small 来初始化 hmap。主要差异在于是否会马上初始化 hash table
    //makemap64:当 hint 类型为 int64 时的特殊转换及校验处理,后续调用 makemap
    //makemap:实现了标准的 map 初始化

    注意:初始化时指定一个合适的容量,可以提升性能,如容量过小,新增的键值对比较多,会导致频繁分配buckets,进行扩容迁移等,这样就会造成性能下降。

    二、取值

    var m = map[int]bool{
        1:true,
        2:false,
    }
    
    v1 := m[3]
    log.Println(v1) //输出:false
    
    v2,ok := m[3]
    log.Println(v2,ok)  //输出:false false
    
    //底层函数
    //func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer
    //func mapaccess2(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, bool)

    三、平时使用需要注意的地方

      1、作为函数参数传递(make map 返回的是一个指针 *hmap)

    func main(){
        var m = make(map[int]bool)
    
        f(m)
    
        log.Println(m) //输出:map[1:true]
    }
    
    func f(m map[int]bool){
        if m != nil{
            m[1] = true
        }
    }

      2、map非线程安全,再查询、赋值、遍历、删除的时候会检查写标志,如果发现写标志位置等于1,直接panic。

    if h.flags&hashWriting != 0 {
        throw("concurrent map writes")
    }

      3、map中的key是无序的

      这里主要有两点造成了每次遍历的数据顺序不一样:

      (1)、map在扩容以后,发生了旧bucket中的key部分迁移或全部迁移到新bucket中,这时候已经就无法还原以前的顺序了。

      (2)、go故意的,开始遍历的时候不是固定0 bucket开始的,每次都会随机值的bucket开始遍历,并且从这个bucket中随机一个元素开始遍历,这样做以为为了让程序员知道不管这样遍历map的顺序都是不一样的。  

    var m = make(map[int]bool)
    
    for i := 0; i < 5; i++ {
        if i%2 == 0 {
            m[i] = true
        } else {
            m[i] = false
        }
    }
    
    for k, v := range m {
        log.Println(k, v)  //每次输出可以能都不一样
    }
    
    [源码]
    //生成随机数 r
    r := uintptr(fastrand())
    if h.B > 31-bucketCntBits {
        r += uintptr(fastrand()) << 31
    }
    
    // 从哪个 bucket 开始遍历
    it.startBucket = r & (uintptr(1)<<h.B - 1)
    // 从 bucket 的哪个 cell 开始遍历
    it.offset = uint8(r >> h.B & (bucketCnt - 1))

    四、hmap+bmap的一些结构理解

      网上对这部分都写得比较多了,这里主要看看bmap的结构,再存储k 和 v 的载体并不是用 k/v/k/v/k/v/k/v 的模式,而是 k/k/k/k/v/v/v/v 的形式去存储,我们来调试看看。

    const (
        // Maximum number of key/elem pairs a bucket can hold.
        bucketCntBits = 3
        bucketCnt     = 1 << bucketCntBits
    )
    
    // 这些struct都是在源码中复制出来的
    type hmap struct {
        count     int    // 元素个数,调用 len(map) 时,直接返回此值
        flags     uint8  //状态标识,主要是 goroutine 写入和扩容机制的相关状态控制。并发读写的判断条件之一就是该值
        B         uint8  // 桶,最大可容纳的元素数量,值为 负载因子(默认 6.5) * 2 ^ B,是 2 的指数
        noverflow uint16 // 溢出桶的数量
        hash0     uint32 // 哈希因子(哈希函数的算法与key的类型一一对应的。根据 key 的类型, maptype结构体的 key字段的alg 字段会被设置对应类型的 hash 和 equal 函数,go 里面有aeshash算法和memhash算法,amd64 使用aeshash算法)
    
        buckets    unsafe.Pointer // 保存当前桶数据的指针地址(指向一段连续的内存地址,主要存储键值对数据)
        oldbuckets unsafe.Pointer // 保存旧桶的指针地址
        nevacuate  uintptr        // 迁移进度
        //extra *mapextra // 当 key 和 value 都可以 inline 的时候,就会用这个字段
    }
    
    //源码中的结构,再编译的时候会在struct中增加几个字段
    //type bmap struct {
    //    tophash [bucketCnt]uint8
    //}
    type bmap struct {
        topbits  [bucketCnt]uint8
        keys     [bucketCnt]int  //keytype 这里测试写的int
        values   [bucketCnt]bool //valuetype 这里测试写的bool
        pad      uintptr
        overflow uintptr
    }
    
    func main() {
        //初始化一个map
        var m = make(map[int]bool)
        //赋值
        for i := 0; i < 5; i++ {
            if i%2 == 0 {
                m[i] = true
            } else {
                m[i] = false
            }
        }
        //强制转换获取一个hmap对象
        h := **(**hmap)(unsafe.Pointer(&m))
    
        log.Println(h.count) //输出:5
    
        //获取第一个bucket
        b := (*bmap)(unsafe.Pointer(h.buckets))
    
        log.Println(b)
    }

      b keys 和 values的存储

     以后遇到其它情况再来补充。

  • 相关阅读:
    荔枝派Nano (Lichee Pi)玩 Linux 傻瓜教程 (5) --- 虚拟USB网卡,SSH登录
    荔枝派Nano (Lichee Pi)玩 Linux 傻瓜教程 (4) --- 安装Python
    荔枝派Nano (Lichee Pi)玩 Linux 傻瓜教程 (3) --- TF卡扩容
    荔枝派Nano (Lichee Pi)玩 Linux 傻瓜教程 (2) --- 文件传输
    荔枝派Nano (Lichee Pi)玩 Linux 傻瓜教程 (1) --- 烧录,数据线连接登录
    RETRO MACHINE(迷你FC街机/掌机)Dump及改造
    Jetson (4)--- 人脸识别(OpenCV安装)
    Jetson (3)--- 人脸识别(Dlib安装)
    Jetson (2)--- 环境配置
    Jetson (1)--- 金属盒子按键Power,Reset线接法
  • 原文地址:https://www.cnblogs.com/fanxp/p/13776887.html
Copyright © 2020-2023  润新知