go map

哈希表是计算机里面比较重要的一个数据结构, 虽然各个哈希表的实现不同, 但是基本上不同的实现都提供快速的增、删、改、查功能。 go语言用map来提供哈希表的功能。

声明和初始化

go语言的map有如下的形式: map[KeyType]ValueType    

其中, 要求KeyType必须是 可以比较的( 可以使用==和!=号), ValueType可以是任何类型, 当然这个任意类型也包括另外一个map,比如 map[string]map[string]int 是一个合法的map。

如果有如下语句: var m map[string]int ,  那么变量m就是一个key为string类型, value为int类型的map,因为map是引用类型(和go里面的slice一样), 所以m的值是nil,它没有指向任何内部的map数据结构,你可以读这个m,但是写它是会panic掉的,所以你最好不要这样来声明m, 你可以用go自带的make函数: m = make(map[string]int) 来初始化m,这个时候m不是nil了, 你既可以读又可以写这个m了。 当然你也可以用  m = map[string]int{} 来初始化一个空的map, 这个和上面的make函数效果是一样的, 但是我个人还是推荐用make函数来初始化map。

使用map

go为你提供了你熟悉的方式来操作map, 比如 m[“route”] = 66 把key”route”赋了值66。 读取这个key的值并赋给一个新的变量可以用: i := m[“route”] 语句。 如果key不存在, 那么得到的值将是ValueType的 零值, 比如 z := m[“not-exist”] ,因为m的值类型是int型的, 所以z的值会为int类型的零值, 也就是0.

我们可以通过内置函数len来得到map里面元素的个数, 比如len(m)会返回m里面有多少个元素。 如果你想删除map里面的一个特定的key, 你可以用go内置的delete函数, 比如delete(m, “to-be-deleted”) 来删除可以为 “to-be-deleted”的元素。 delete函数没有任何返回值, 如果要删除的key不存在, 那么就不会对这个map做任何修改。 

刚才我们说过, 如果取值的时候, key不存在, 那么将返回值类型的零值, 那么假如有如下语句: a := m[“mykey”] , 通过这个语句, a的值为0, 那么我们怎么知道是mykey不存在, 还是说mykey存在并且对应的value值为0呢? go为我们提供了一种方式来区分这个情况, a, ok := m[“mykey”] ,如果mykey不存在,则ok为false, 如果mykey存在, 则ok为true。 当然如果只想确认一个key是否存在, 而不关心这个key对应的value的话, 那么用_, ok = m[“mykey”], 然后判断ok是否为true就行了。

如果你想要遍历map里面的每一个元素, 你可以试用range语句:

for key, value := range m {
    fmt.Println("Key:", key, "Value:", value)
}

也可以在声明的时候直接把一些元素初始化出来:

commits := map[string]int{
    "rsc": 3711,
    "r":   2138,
    "gri": 1908,
    "adg": 912,
}

利用零值

我们可以利用“map在key不存在的情况下返回value的零值”这个特性, 做一些事情。 比如我们可以用map来模拟集合,valueType为布尔类型(布尔类型的零值是false)。下面的例子遍历Node类型的链表,并且打印遍历的值,它利用一个Node指针的map来避免出现环形链表(这样遍历永远不会结束)。

type Node struct {
        Next  *Node
        Value interface{}
    }
    var first *Node

    visited := make(map[*Node]bool)
    for n := first; n != nil; n = n.Next {
        if visited[n] {
            fmt.Println("cycle detected")
            break
        }
        visited[n] = true
        fmt.Println(n.Value)
    }

表达式visisted[n]的值为true如果n被访问过, 为false如果n没有被访问过。 我们不用那种_, ok = visited[n], 然后判断ok的方式来判断n是否出现过, 利用布尔类型的零值为false可以像上面的代码一样简洁的把这件事情干了。

另外一个利用零值的例子是一个slice的map, append一个nil的slice会自动分配一个新的slice,所以没有必要先判断slice是否为空再append。在下面的例子中,people是一个slice,里面放着结构为Person的数据, Person有Name属性和Likes属性, Likes是这个人的喜好的slice。

type Person struct {
        Name  string
        Likes []string
    }
    var people []*Person

    likes := make(map[string][]*Person)
    for _, p := range people {
        for _, l := range p.Likes {
            likes[l] = append(likes[l], p) // no need to check if like[l] is nil
        }
    }

这样打印喜欢cheese的人的列表:

for _, p := range likes["cheese"] {
        fmt.Println(p.Name, "likes cheese.")
    }

打印有多少人喜欢bacon:

fmt.Println(len(likes["bacon"]), "people like bacon.")

key的类型

上面我们说过, map的key必须是 可比较 的(其实自己实现过hash表的人也应该知道, 我们一般对key做hash,然后把hash相同的key放到一个桶上, 有冲突的时候可以挂链,在遍历这个链的时候需要比较key才能找到这个key), 语言规范 对可以比较做了准确的描述,用一个简短的话来说, 布尔型, 数值,字符串,指针,channel和interface都是可以比较的,只包含以上类型的数组或者struct也是可比较的。从上面可以看到, slices,map,函数,都是不可比较的, 所以不能用作map的key(但是可以作为map的value)。 很明显数值类型,字符串类型和其他基本类型可以用来做key,但是struct做key不是特别明显,我们就用一个列子来说明有些时候用struct做key可以使代码更简单。下面的结构(map的map)可以用来按国家统计一个url被访问的次数。

hits := make(map[string]map[string]int)

这个是一个字符串到(字符串到int的map)的map,外层的key表示一个url, 里层的map的key表示国家。下面的代码用来取澳大利亚访问/doc的次数:

n := hits["/doc/"]["au"]

当我们更新一个国家的访问次数的时候, 会很麻烦, 对于每一个外层的key,我们都得判断里层的map是否存在, 如果不存在, 我们要新建一个map:

func add(m map[string]map[string]int, path, country string) {
    mm, ok := m[path]
    if !ok {
        mm = make(map[string]int)
        m[path] = mm
    }
    mm[country]++
}
add(hits, "/doc/", "au")

如果我们用一个struct做key的话, 就不用这么麻烦的判断内层的map是否为空了:

type Key struct {
    Path, Country string
}
hits := make(map[Key]int)

hits[Key{"/doc", "au"}]++

取澳大利亚访问/doc的次数也很简单:

n := hits[Key{"/doc", "au"}]

map不是并发访问安全的 ,同时读写同一个map的行为是未定义的,所以你要在多个goroutine里面操作同一个map,需要同步原语来保护的, 比较常见的方法是用 sync.RWMutex . 。 下面的代码定义了一个counter,这个counter是一个匿名struct类型的,这个匿名struct里面就包含了一个map和 嵌入 的sync.RWMutex . 。

var counter = struct{
    sync.RWMutex
    m map[string]int
}{m: make(map[string]int)}

在读的时候, 需要加读锁:

counter.RLock()
n := counter.m["some_key"]
counter.RUnlock()
fmt.Println("some_key:", n)

在写的时候, 需要加写锁:

counter.Lock()
counter.m["some_key"]++
counter.Unlock()

遍历顺序

map遍历的时候,返回的key顺序是 随机 的,而且不保证两次遍历的顺序是相同的, 不要依赖于这个顺序。

本文主要参考的是 https://blog.golang.org/go-maps-in-action, 如有说的不对的地方, 欢迎指正。

03-12
<think>好的,我需要帮助用户了解Go语言中Map的用法。首先,我应该回顾用户提供的引用内容,确保回答准确。根据引用[2],MapGo中的格式是map[K]T,K必须是可比较类型,而Map是引用类型。引用[4]提到Map的零值是nil,使用前必须用make初始化,否则会panic。引用[5]指出Map是无序的,迭代顺序不固定,因为是哈希表实现。引用[3]还提到不能直接用==比较Map,只能检查是否为nil。 接下来,我需要组织回答的结构。用户可能需要基础使用、初始化、增删改查、遍历、注意事项等方面。要分步骤讲解,每个部分给出代码示例,并引用相关文献。例如,初始化部分需要强调make的重要性,引用[4]。遍历时说明无序性,引用[5]。比较Map时提到不能用==,引用[3]。 还要注意用户提供的系统级指令,比如行内公式用$...$,但这里可能用不上。独立公式可能需要用$$,但Map教程可能不涉及数学公式。需要确保使用中文,生成相关问题,并在引用处添加标识。例如,在提到Map初始化时,末尾添加[^4]。 在生成相关问题部分,要考虑用户可能关心的并发安全、性能优化、与其它结构的对比等。引用[1]提到未来可能引入更多数据结构,可以建议相关问题。例如,Map的并发安全方法,优化技巧,与slice的区别等。 需要检查所有引用是否准确对应内容,比如引用[3]关于Map比较的部分,引用[5]关于迭代无序。避免错误引用,比如引用[1]更多是概述,可能用在结语部分。同时,确保代码示例正确,比如初始化、删除操作等。 最后,保持回答结构清晰,分点说明,每个部分有小标题,代码用代码块包裹,语言标记为go。确保回答真实可靠,符合Go语言官方文档的规范,并正确引用提供的资料。</think>### Go语言Map使用教程 #### 1. Map的基本概念 MapGo语言中的一种内置数据结构,用于存储键值对,格式为 `map[K]T`,其中 `K` 是键的类型(必须是可比较类型),`T` 是值的类型。Map是引用类型,零值为 `nil`,直接操作未初始化Map会触发运行时错误。 --- #### 2. Map初始化 - **使用`make`函数创建** 必须通过 `make` 函数初始化后才能操作: ```go m := make(map[string]int) // 初始化一个键为string、值为int的Map ``` - **字面量初始化** 可在声明时直接赋值: ```go m := map[string]int{ "apple": 5, "banana": 3, } ``` --- #### 3. Map的增删改查 - **添加或修改元素** ```go m["orange"] = 4 // 添加键"orange",值4 m["apple"] = 10 // 修改键"apple"的值为10 ``` - **删除元素** 使用 `delete` 函数: ```go delete(m, "banana") // 删除键"banana" ``` - **查询元素** 通过键访问值,并检查是否存在: ```go value, exists := m["apple"] if exists { fmt.Println(value) // 输出10 } ``` --- #### 4. Map的遍历 Map是无序的,遍历顺序不固定(由哈希表实现决定)[^5]: ```go for key, value := range m { fmt.Printf("Key: %s, Value: %d\n", key, value) } ``` --- #### 5. 注意事项 1. **不可用`==`比较Map** Map不能直接通过 `==` 运算符比较(只能检查是否为 `nil`)[^3]。 ```go if m1 == nil { /* 合法 */ } if m1 == m2 { /* 非法 */ } ``` 2. **并发安全问题** Map非线程安全,并发读写需通过锁或使用 `sync.Map`(适用于读多写少场景)[^4]。 3. **值为复合类型时的初始化** 若值为切片或嵌套Map,需逐层初始化: ```go nestedMap := make(map[string]map[int]string) nestedMap["a"] = make(map[int]string) // 内层Map需单独初始化 nestedMap["a"][1] = "test" ``` --- #### 6. 应用场景 - 快速查找、去重统计(如统计单词频率)。 - 缓存数据(如存储用户会话信息)。 - 配置管理(如键值对形式的参数配置)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值