go基础——map/sync.Map

2020-09-04  本文已影响0人  chase_lwf

注:权作为学习笔记,基于version: 1.14

内容

一 map
1.1 数据结构
1.2 写入
1.3 查找
1.4 扩容
1.5 注意点

二 sync.map
2.1 基本使用
2.2 实现原理

一 map

1.1 数据结构

学习go中map的数据结构,可以对比着java中的hashmap实现来一起对比学习,java 中map是采用拉链罚来解决hash冲突,基本数据结构是数组+链表的hash表形式;而go map并没有采取这种形式,go中采取buckets的形式,每个bucket在固定了可以存储8对键/值,总体数据结构大体如下图这个样子:


image.png

源码长这个样子:

// src/runtime/map.go
type hmap struct {
    count        int               /* Map中元素数量 */
    flags        int8              /* 相关标志位 */
    B            int8              /* (1<< B * 6.5)为最多元素的数量 ; 代表bucket的个数,不是直接B个桶,是当前map有2^B个桶;*/
    noverflow    int16             /* 溢出桶的数量 */
    hash0        uint32            /* 哈希种子 */
    buckets      unsafe.Pointer    /* 桶指针 */
    oldbuckets   unsafe.Pointer    /* 桶指针(只有扩容的时候才使用,指向旧的桶) */
    nevacuate    uintptr           /* 用于桶搬迁 */
    extra        *mapextra         /* 当key/value非指针时使用 */
}

type mapextra struct {
    overflow      *[]*bmap         /* 溢出桶的指针 */
    oldoverflow   *[]*bmap             
    nextOverflow  *bmap                   
}

type bmap struct {
    tophash  [bucketCnt]uint8      /* bucketCnt=8,一个桶只能存放8对k/v, 低B位用来寻找桶,高8位用来寻找元素 */
}

/* 当kev/value不为指针时,溢出桶存放到mapextra结构中,overflow存放buckets中的溢出
    桶,oldoverflow存放oldbuckets中的溢出桶,nextOverflow预分配溢出桶空间 */
type mapextra struct {
    overflow        *[]*bmap       /* 以切片形式存放buckets中的每个溢出桶 */
    oldoverflow     *[]*bmap       /*  以切片形式存放oldbuckets中的每个溢出桶*/
    nextOverflow    *bmap
}

/* map标志位 */
iterator           1        /* 迭代器buckets桶的标志位,为1表示正在使用buckets*/
oldIterator        2        /* 迭代器oldbuckets桶的标志位 ,为1表示正在使用oldbuckets*/
hashWriting        3        /* 并发写标志位,为1表示有goroutine正在写map */
sameSizeGrow       4        /* 等量扩容标志,表示申请的桶数量和原来一样 */

总体来说:

1.2 写入

put的大体步骤是:

1.3 查找

查找和写入步骤很类似,查找时会利用 bmap的tophash快速对比,如果key的前8位和和tophash里的值都不一样,那证明不在当前桶,可能在溢出桶里,加快遍历速度

1.4 扩容

当满足一下任何一个条件时,会发生扩容:

1.5 注意点

var map1 map[string]int
map1["ss"] = 1 //  直接panic, map1是nil

二 sync.map

map是非线程安全的,在go中如果想用线程安全的map, 一般有两种方式:

type SyncMap struct {
    sync.RWMutex
    m map[string]string
}

2.1 基本使用

func main(){
    var sm sync.Map
    // 写入
    sm.Store("a", 1)
    sm.Store("b", 2)
    sm.Store("c", 3)

    // 取出
    v, _ := sm.Load("a")
    fmt.Println(v.(int))

    // 删除
    sm.Delete("b")

    // 存在则获取 不存在则添加
    sm.LoadOrStore("d", 4)

    // 遍历
    sm.Range(func(key, value interface{}) bool {
        fmt.Println(key.(string), value.(int))
        return true
    })
}

2.2 基本原理

基本数据结构如下:

type Map struct {
    mu Mutex
    read atomic.Value // readOnly
    dirty map[interface{}]*entry
    misses int
}

type readOnly struct {
    m       map[interface{}]*entry
    amended bool // true if the dirty map contains some key not in m.
}

详细理解:

引用:
1 Golang之Map源码解析:https://www.jianshu.com/p/17f49e562f7b
2 大话图解golang map https://studygolang.com/articles/21047?fr=sidebar
3 《go 语言设计与实现》

上一篇 下一篇

猜你喜欢

热点阅读