map

作者: 我是聪 | 来源:发表于2023-04-01 22:50 被阅读0次

    简介

    在 Go 语言中,Map 是一种键值对数据结构,类似于其它语言中的字典或哈希表,用于存储无序的键值对数据,其中每个键唯一对应一个值。

    Map 的定义形式为:map[keyType]valueTyp

    // 创建一个空的 map
    var personMap map[string]int
    
    // 初始化一个 map
    personMap = map[string]int{
        "Tom":  25,
        "John": 32,
        "Amy":  19,
    }
    
    // 向 map 中添加新的键值对
    personMap["Jack"] = 27
    
    // 访问 map 中的元素
    fmt.Println(personMap["Tom"]) // 输出:25
    
    // 删除 map 中的元素
    delete(personMap, "Amy")
    
    // 遍历 map 中的键值对
    for name, age := range personMap {
        fmt.Printf("%s is %d years old\n", name, age)
    }
    
    

    上述代码创建了一个 personMap 的 map 变量,其中键为字符串类型,值为整数类型,表示人名和年龄的键值对。
    接着通过 map[key] = value 的方式向 map 中添加新的键值对,通过 map[key] 的方式访问 map 中的元素。此外,还可以通过 delete(map, key) 的方式删除 map 中的元素,通过 for range 的方式遍历 map 中的键值对

    底层实现

    Go语言中的Map使用哈希表(Hash Table)来实现。哈希表是一种由键值对组成的数据结构,它可以支持常数级别的插入、删除和查找操作,因此在处理大量数据时非常高效。

    在Go语言中,Map底层实现是一个哈希表数组。每个哈希表包含了若干个桶(Bucket),每个桶中存储了若干个键值对。Map中的每个键值对存储在一个Entry结构体中,其中Key字段是键,Value字段是值。哈希表中的每个桶是一个链表,每个链表节点都是一个Entry结构体。

    以下是一个简单的 Entry 结构体的代码实现示例

    type Entry struct {
        Key   string      // 键
        Value interface{} // 值
        Hash  uint64      // 哈希值
        Next  *Entry      // 指向链表中下一个元素的指针
    }
    
    

    这个 Entry 结构体包含了四个字段:

    Key 表示键,使用 string 类型来存储。
    Value 表示值,使用 interface{} 类型来存储,这样可以存储任何类型的值。
    Hash 表示哈希值,使用 uint64 类型来存储,这是为了支持大量数据的快速哈希计算。
    Next 表示链表中下一个元素的指针,使用 *Entry 类型来存储,这是为了支持链表解决哈希冲突。
    这个 Entry 结构体实现了哈希表中的键值对,同时支持解决哈希冲突的链表操作。在使用哈希表时,可以将多个键值对存储在一个 Entry 结构体中,然后将这个结构体存储到哈希表的对应位置中。

    下面是一个简单的 map 的底层桶的代码实现示例,其中桶的数量为 16

    type Node struct {
        key   string
        value interface{}
        next  *Node
    }
    
    type Map struct {
        buckets []*Node
    }
    
    func NewMap() *Map {
        m := &Map{make([]*Node, 16)}
        return m
    }
    
    func (m *Map) Get(key string) interface{} {
        index := hash(key) % len(m.buckets)
        node := m.buckets[index]
        for node != nil {
            if node.key == key {
                return node.value
            }
            node = node.next
        }
        return nil
    }
    
    func (m *Map) Set(key string, value interface{}) {
        index := hash(key) % len(m.buckets)
        node := m.buckets[index]
        for node != nil {
            if node.key == key {
                node.value = value
                return
            }
            node = node.next
        }
        newNode := &Node{key, value, m.buckets[index]}
        m.buckets[index] = newNode
    }
    
    func hash(key string) int {
        h := 0
        for i := 0; i < len(key); i++ {
            h = 31*h + int(key[i])
        }
        return h
    }
    
    

    在这个实现中,使用了一个 Map 结构体,其中有一个 buckets 成员变量,表示桶的数组,每个桶是一个链表,用 Node 结构体表示。Set 方法将键值对添加到桶中,如果桶中已经存在该键,就更新对应的值;如果桶中不存在该键,就添加一个新的节点到链表的头部。Get 方法通过键获取值,根据键计算出桶的索引,然后在链表中查找该键对应的节点,如果找到了就返回节点的值。hash 函数使用了一个简单的哈希算法来将字符串键映射到整数索引。

    当插入一个键值对时,首先需要计算键的哈希值,然后根据哈希值找到对应的哈希表和桶。如果桶为空,则直接将键值对插入桶中;否则需要遍历桶中的链表,查找键是否已经存在。如果键已经存在,则更新对应的值;否则将键值对插入链表的末尾。

    当查找一个键值对时,也需要先计算键的哈希值,然后根据哈希值找到对应的哈希表和桶。然后遍历桶中的链表,查找键对应的值。

    当删除一个键值对时,也需要先计算键的哈希值,然后根据哈希值找到对应的哈希表和桶。然后遍历桶中的链表,查找键对应的节点,并删除节点。

    总的来说,Go语言中的Map底层实现是非常高效的,可以在常数级别的时间内完成插入、删除和查找操作。但是需要注意的是,在使用Map时需要保证键类型的可比较性,否则程序会在运行时报错。
    以下是Go语言中map的部分实现代码(摘自Go语言1.16.5版本)

    type hmap struct {
        count     int // map中的元素数量
        flags     uint8 // 可以存储一些标记信息,例如map是否被锁定等
        B         uint8 // 桶数量的对数,即2^B个桶
        noverflow uint16 // 溢出桶数量
        hash0     uint32 // hash种子,用于计算哈希值
        buckets   unsafe.Pointer // 桶指针,指向底层哈希表的桶数组
        oldbuckets unsafe.Pointer // 指向老的桶数组,用于rehash
        nevacuate uintptr // 下一次evacuate过程的位置
        extra *mapextra // 一些额外信息,例如指向溢出桶的指针
    }
    
    type mapextra struct {
        overflow    *[]*bmap // 溢出桶指针数组
        oldoverflow *[]*bmap // 老的溢出桶指针数组,用于rehash
        nextOverflow *bmap    // 下一个要分配的溢出桶
    }
    
    type bmap struct {
        tophash [bucketCnt]uint8
        // data: 记录了桶中所有key-value对的信息
        // 每个key-value对由1个key和1个value组成,key和value的类型都是interface{},但是实际上key和value的类型是具体的类型,而不是interface{}
        // key和value都需要通过interface转换才能被使用
        data  [bucketCnt]struct {
            key, value unsafe.Pointer
        }
        // 一个bucket可能会分配多个溢出桶,用overflow字段指向第一个溢出桶
        overflow *bmap
    }
    
    
    

    相关文章

      网友评论

          本文标题:map

          本文链接:https://www.haomeiwen.com/subject/cxylddtx.html