[Go语言入门] 10 Go语言Map详解

10 Go语言Map详解

Map是一种无序的键值对的集合,这是一种通过key来快速获取、更新或移除键对应的值的结构。

Go的Map是用Hash表实现的,无论Map有多大,这些操作都可在一个常量时间内就能完成。

Go语言中定义map类型时需要指明键类型和值类型,map[k]V,其中k和V是键和值的类型,键的类型k必须是可以通过==操作符来比较的数据类型。

我们可以像迭代数组和切片那样迭代Map,但是因为Map是无序的,所以我们在迭代时无法确定元素顺序。


10.1 Map的创建和基本使用

声明Map变量
// name 变量名称, k 键的数据类型, V 值的数据类型
var name map[k]V

上面的语句声明了map型变量,该map的所有键都是k类型,所有值都是V类型。由于没有显示初始化,该变量的值被编译器初始化为nil。

Key类型可以是任意支持==或者!=操作符比较的类型,比如string、int、float。因为数组、切片、和结构体不支持==和!=操作符比较,所以他们不能作为map的Key,但指针和接口类型可以。

Value类型可以是任何类型。

示例:

var m1 map[string]int
var m2 map[int]string
var m3 map[string][]byte

创建Map

通过Map字面量创建,例如:

var m1 = map[string]int {
    "hello": 101,
    "world": 102,
    "i": 103,
    "love": 104,
    "go": 105,
}

var m2 = map[string]int{}	// 空map,与nil map不同

通过make()函数创建,例如:

var m1 = make(map[string]int)	// 空map

访问键值对

通过键来访问对应的值。

var m1 = map[string]int {
    "hello": 101,
    "world": 102,
    "i": 103,
    "love": 104,
    "go": 105,
}

var cnt int

// 获取键的值:

// 如果键存在,返回对应的值
cnt = m1["hello"]
fmt.Println(cnt)			// 101

// 如果键不存在,返回值类型的零值
cnt = m1["monday"]
fmt.Println(cnt)			// 0 		-- 得到该类型的零值


// 向键赋值:

// 如果键存在,则修改其值
m1["hello"] = 30
fmt.Println(m1["hello"])	// 30

// 如果键不存在,则新增这个键值对
m1["sunday"] = 40
fmt.Println(m1["sunday"])	// 40

如果我们获取一个不存在的键的值,将会得到其零值。这就导致我们不知道到底是不存在该键、存在该键但值为零值。为了解决这个问题,Go为我们提供了一个检测键是否存在的方法:

var m1 = map[string]int {
    "hello": 101,
    "world": 102,
    "i": 103,
    "love": 104,
    "go": 105,
}

cnt, ok := m1["monday"]
fmt.Println(cnt, ok)		// 0 false

cnt, ok = m1["hello"]
fmt.Println(cnt, ok)		// 101 true

删除键值对

使用Go内置的函数delete()。

var m1 = map[string]int {
    "hello": 101,
    "world": 102,
    "i": 103,
    "love": 104,
    "go": 105,
}

delete(m1, "hello")

cnt, ok = m1["hello"]
fmt.Println(cnt, ok)		// 0 false
fmt.Println(m1)				// map[go:105 i:103 love:104 world:102]

delete()函数接受两个参数,第一个是要操作的map,第二个是要删除的键。

delete()函数删除不存在的键也是可以的,只是不其任何作用而已。


遍历map

使用for-range遍历:

var m1 = map[string]int {
    "hello": 101,
    "world": 102,
    "i": 103,
    "love": 104,
    "go": 105,
}

for k, v := range m1 {
    fmt.Println(k, v)
}

// 输出
// hello 101
// world 102
// i 103
// love 104
// go 105

range返回两个值,第一个是键,第二个是值。

这种遍历是无序的。如果想按序遍历,可以先把map中的键都取出来,排序后再按键遍历。例如:

package main

import (
	"fmt"
    "sort"
)

func main() {
    var m1 = map[string]int {
        "hello": 101,
        "world": 102,
        "i": 103,
        "love": 104,
        "go": 105,
    }

    var keys []string

    for k := range m1 {
        keys = append(keys, k)
    }

    sort.Strings(keys)

    for _, k := range keys {
        fmt.Println(k, m1[k])
    }
}

// 输出
// go 105
// hello 101
// i 103
// love 104
// world 102

Map的容量

map可以根据新增的键值对动态的伸缩,它的容量不存在限制。可以在创建map的时候指定初始容量。例如:

m1 := make(map[string]int, 100)

当map中的键值对达到容量上限时,如果在新增键值对,map的容量会自动扩展。


10.2 Map作为函数参数

Map类型是引用类型,让我们通过下面的代码看一下Map类型变量占用多少内存:

package main

import (
	"fmt"
	"unsafe"
)

func main() {
    var m1 = map[string]int {
        "hello": 101,
        "world": 102,
        "i": 103,
        "love": 104,
        "go": 105,
    }
	
	fmt.Println(unsafe.Sizeof(m1))			// 输出 8
}

由上面的输出可知,map类型的变量仅占8字节(在64位系统上是8字节,在32位系统上是4字节)。事实上,在Go语言中声明一个map型变量,该变量的内存结构中仅仅包含一个地址,这个地址指向了真正的map对象。
在这里插入图片描述

当把一个map型变量赋值给别的变量,或者将一个map变量传递给函数参数时,复制的也仅仅是上图中的那个地址。也就是说,当把一个map传给一个函数,函数内的map和函数外的map引用的是同一个map对象。

func main() {
    var m1 = map[string]int {
        "hello": 101,
        "world": 102,
        "i": 103,
        "love": 104,
        "go": 105,
    }
    
    myFunction(m1)
    fmt.Println(m1["hello"])								// 100
    fmt.Printf("main函数中m1指向的map对象的地址:%p\n", m1)		// main函数中m1指向的map对象的地址:0xc0000b6330
    fmt.Printf("main函数中m1变量的地址:%p\n", &m1)		      // main函数中m1变量的地址:0xc0000cc018
}

func myFunction(m map[string]int) {
    m["hello"] = 100
    fmt.Printf("myFunction函数中map对象的地址:%p\n", m)		// myFunction函数中map对象的地址:0xc0000b6330
    fmt.Printf("myFunction函数中m变量的地址:%p\n", &m)		// myFunction函数中m变量的地址:0xc0000cc020
}

上面的这个例子中,main函数中的map变量和myFunction函数中的map变量的地址是不同的,但这两个函数中的map变量所指向的map对象的地址是一样的。因此myFunction函数内对map的修改对main函数中的map也生效。


10.3 Map使用注意事项

nil map

大多数的map操作都可以安全的在map的nil上执行,包括查找元素,删除元素,获取map元素个数,执行range循环,这时和操作空map的结果一致。但是向nil map中设置键值会导致宕机异常:

var m1 map[string]int

fmt.Println(m1["hello"])		// 0

delete(m1, "hello")

fmt.Println(len(m1))			// 0

for k, v := range m1 {
	fmt.Println(k, v)
}

// m1["hello"] = 100  // 运行时发生异常:panic: assignment to entry in nil map

不可获取map中元素的地址
m1 := make(map[string]int)
m1["hello"] = 200

_ = &m1["hello"]		// 编译错误: cannot take the address of m1["hello"]

原因有二:

  • map中该键可能不存在
  • 随着map的增长可能会导致已有的键值对被重新散列到新的存储位置

10.4 通过Map实现集合

Go语言没有提供集合类型,但通过Map类型很容易实现集合。即:利用Map类型的键来存储集合元素。

下面的例子实现了一个字符串集合:

set := make(map[string]bool)
set["apple"] = true
set["orange"] = true
set["banana"] = true

if set["apple"] {
    fmt.Println("apple in set")
}

if set["cherry"] {
    fmt.Println("cherry in set")
}

delete(set, "apple")

if set["apple"] {
    fmt.Println("apple in set")
}

Copyright@2022 , 359152155@qq.com

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时空旅客er

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值