第九章:Go语言映射类型map
1. map概述
Go语言中
map
字典类型时散列表(hash table)的实现,因为Go语言中将map中的每个键都看作与其对应元素的索引,所以同一个map中的键都是唯一的.映射map功能强大之处在于能够基于键快速检索数据
map类型可以写成map[K]V ,其中所有的key必须是相同类型,所有的value也是相同类型,但是K也V可以是不同类型
2. map定义
2.1 map 声明
var 变量名 map[key-type]value-type
- 变量名 : 就是定义一个map类型的变量名称
- key-type : map类型数据键的类型 ,通常为 int ,string类型
- key-value : map类型数据值得类型,可以是int ,string ,bool,map,struct等等
map 的声明不会分配内存,初始化需要make,分配内存之后才能使用
package main
import "fmt"
func main(){
// 声明一个键为int型,值为string型的map
var a map[int]string
// 声明一个键为int型,值为float型的map
var b map[int]float64
// 声明一个键为string型,值为bool型的map
var c map[string]bool
// 声明一个键为string型,值为map[int]string型的map
var d map[string]map[int]string
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
}
$ go run main.go
map[]
map[]
map[]
map[]
2.2 map初始化
- map 在使用前一定要make
初始化方式1
package main
import "fmt"
func main(){
var a map[int]string
a = make(map[int]string,5)
a[1] = "a"
a[2] = "b"
a[5] = "e"
a[3] = "c"
a[6] = "f"
fmt.Println("a = ",a)
}
$ go run main.go
a = map[1:a 2:b 3:c 5:e 6:f]
初始化方式2
package main
import "fmt"
func main(){
// 这种写法显得很冗余
var b map[string]string = map[string]string{
"A" :"left",
"D" :"right",
"W":"forward",
}
// 简化写法
fmt.Println("b = ",b)
var b2 = map[string]string{
"A" :"left",
"D" :"right",
"W":"forward",
}
fmt.Println("b2 = ",b2)
}
$ go run main.go
b = map[A:left D:right W:forward]
b2 = map[A:left D:right W:forward]
初始化方式3
package main
import "fmt"
func main(){
c := map[int]string{
1:"状元",
2:"榜眼",
3:"探花",
}
fmt.Println(c)
}
$ go run main.go
map[1:状元 2:榜眼 3:探花]
初始化方式4
package main
import (
"fmt"
)
func main(){
e := make(map[string]string)
e["beijing"] = "北京"
e["shanghai"] = "上海"
fmt.Println(e)
}
$ go run main.go
map[beijing:北京 shanghai:上海]
3. map 基本操作
3.1 增 和 改
map类型数据的增加和修改 ,仅当map中的key不存的时候是新增一个元素,当key存在再给它赋值那就是修改一个key的值了
package main
import "fmt"
func main() {
var m = make(map[int]string)
// 给一个map 新增成员
m[1] = "America"
m[2] = "China"
m[3] = "Russia"
fmt.Println(m)
// 修改map的数据
m[1] ,m[2] = m[2],m[1]
m[3] = "Germany"
fmt.Println(m)
}
$ go run main.go
map[1:America 2:China 3:Russia]
map[1:China 2:America 3:Germany]
3.2 删除
使用Go语言内建函数delete()删除map中的一组键值
对map执行删除元素操作时安全的,即使元素不存在,delete删除一个不存在元素返回率 0
package main
import "fmt"
func main() {
var m = make(map[int]string)
// 给一个map 新增元素
m[1] = "America"
m[2] = "China"
m[3] = "Russia"
fmt.Println(m)
//删除map中的一组键值
delete(m,1)
fmt.Println(m)
}
$ go run main.go
map[1:America 2:China 3:Russia]
map[2:China 3:Russia]
清空map中所有元素,在Go语言中没有直接提供这样的方法和函数,那么清空一个map的唯一途径是重新make一个新的map,让原来的数据块成为"垃圾",由系统的GC执行垃圾回收
package main
import "fmt"
func main() {
m := map[int]string{
1:"北京",
2:"上海",
3:"广州",
4:"深圳",
}
fmt.Println(m)
// 清空一个map
m = make(map[int]string)
fmt.Println(m)
}
$ go run main.go
map[1:北京 2:上海 3:广州 4:深圳]
map[]
3.3 查找
在Go语言中map的查找设计的很简洁明确
使用下标来访问map中的元素的元素输出两个值,第一个是访问元素的值,一个是报告元素是否存在的bool值,我们一般将这个bool值命名为 ok
package main
import (
"fmt"
)
func main(){
var m = map[string]string{
"name":"tom",
"age":"99",
"addr":"beijing",
"work":"programer",
}
//判断是否找到特定的键,只需要看第二个参数返回值是true或者false就可以
//这里的ok是一个变量,用来接收是否找到指定的键的标识
name ,ok := m["name"]
fmt.Println(ok) //true
if ok{
fmt.Println(name) // tom
}
//上面访问map元素的写法,可以合并成一条语句
if addr,ok := m["addr"]; ok{
fmt.Println(addr)
}
nickname,yes:= m["nickname"]
fmt.Println(yes) // false
fmt.Println(nickname)
}
$ go run main.go
true
tom
beijing
false
4. map遍历
map
的遍历通过for - range
结构来完成映射map是无序的集合,所以没有办法预测键值对返回的顺序,故而每次迭代map的顺序也可能不一样.
4.1 简单map结构的遍历
package main
import "fmt"
func main() {
var m = map[string]string{
"name": "tom",
"age": "99",
"addr": "beijing",
"work": "programmer",
}
for k,v := range m{
fmt.Printf("key : %s\t value : %s\n",k,v)
}
}
$ go run main.go
key : addr value : beijing
key : work value : programmer
key : name value : tom
key : age value : 99
4.2 复杂map结构的遍历
package main
import "fmt"
func main(){
var m = make(map[int]map[string]string)
m[1] = make(map[string]string)
m[1]["name"] = "tom"
m[1]["age"] = "99"
m[1]["addr"] = "bejing"
m[2] = make(map[string]string)
m[2]["name"] = "lucy"
m[2]["age"] = "88"
m[2]["addr"] = "shanghai"
for k,v := range m{
fmt.Println("k = ",k)
for k1,v1 := range v{
fmt.Println(k1,"===",v1)
}
}
}
$ go run main.go
k = 1
name === tom
age === 99
addr === bejing
k = 2
name === lucy
age === 88
addr === shanghai
4.3 map的比较
map和slice 一样是不能进行比较的,唯一能做的比较是和 nil 比较.
要判断两个map是否拥有相同的键和值,必须写循环判断
示例代码只展示一个思路
package main
import "fmt"
func mapCompare(x, y map[string]string) bool {
// 比较长度
if len(x) != len(y) {
return false
}
// 比较键值
for k, xv := range x {
if yv, ok := y[k]; !ok || yv != xv {
return false
}
}
return true
}
func main() {
var xm = map[string]string{
"name": "tom",
"addr": "beijing",
}
var ym = map[string]string{
"name": "tom",
"addr": "beijing",
}
// map 类型只能和nil 直接比较
if xm == nil || ym == nil {
fmt.Println("map xm or ym is nil")
} else {
fmt.Println("map xm and ym is ok")
}
// 比较两个map是否拥有相同的键值
if ok := mapCompare(xm,ym) ;ok{
fmt.Println("xm 和 ym 有相同的键和值")
}
}
$ go run main.go
map xm and ym is ok
xm 和 ym 有相同的键和值
5. 补充点
5.1 并发读写
并发对一个map进行读写操作会引发竞态问题
package main
func main() {
var m = make(map[int]int)
// 并发函数不断对map进行读写的时候,发生竞态问题
go func() {
for {
m[1] = 1
}
}()
go func() {
for {
_ = m[1]
}
}()
for {
}
}
$ go run main.go
fatal error: concurrent map read and map write
面临这样的问题,可以采取的一个方案是使用 sync.Map 这是sync包的一个特殊结构,与map很不同,相比较map 是损失了性能,解决的并发读写问题,如果没有并发读写问题,直接使用map即可
package main
import (
"fmt"
"sync"
"time"
)
func main() {
// 定义一个sync.Map类型
var sm sync.Map
// 给 sm 添加元素
sm.Store("name", "zhangshan")
sm.Store("addr", "bejing")
sm.Store("job", "programmer")
//获取一个元素
name, ok := sm.Load("name")
if ok {
fmt.Println(name)
}
// 删除一个元素(安全删除)
sm.Delete("nameNone")
// 遍历sm中的元素
sm.Range(func(key, value interface{}) bool {
fmt.Println(key,"===>",value)
return true
})
// 测试并发读写使用
go func() {
for {
t := time.Now().Unix()
sm.Store("name", t)
}
}()
go func() {
for {
name, _ := sm.Load("name")
fmt.Println(name)
}
}()
for {
}
}