Golang映射类型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 {

	}
}

### 实现 GolangMap 的深拷贝 在 Golang 中,直接赋值或使用内置 `copy` 函数仅能实现浅拷贝。对于包含指针或其他复杂类型map,这可能导致意外的行为。为了安全地复制整个结构及其内部数据,需要编写自定义逻辑来执行深拷贝。 针对不同类型的键和值组合,可以采用多种方法: #### 方法一:通过反射机制 这种方法适用于未知类型map 或者当不想为每种可能的情况都写特定代码时。它利用了 Go 的 reflect 包来进行类型检查并递归处理嵌套的数据结构[^1]。 ```go package main import ( "fmt" "reflect" ) func DeepCopyMap(m interface{}) (interface{}, error) { val := reflect.ValueOf(m) if val.Kind() != reflect.Map { return nil, fmt.Errorf("input should be a map") } newMap := reflect.MakeMap(val.Type()) for _, key := range val.MapKeys() { oldValue := val.MapIndex(key) var newValue reflect.Value // 如果 value 是指针,则解引用后再复制 if oldValue.Kind() == reflect.Ptr && !oldValue.IsNil() { elemType := oldValue.Elem().Kind() switch elemType { case reflect.Struct, reflect.Slice, reflect.Map: copiedElem, err := DeepCopyMap(oldValue.Interface()) if err != nil { return nil, err } newValue = reflect.ValueOf(copiedElem).Elem() default: newValue = oldValue.Elem() } newPtr := reflect.New(newValue.Type()).Elem().Set(newValue) newMap.SetMapIndex(key, newPtr.Addr()) } else { // 对于非指针类型直接设置新值 var err error if oldValue.Kind() == reflect.Struct || oldValue.Kind() == reflect.Slice || oldValue.Kind() == reflect.Map{ copiedVal, err := DeepCopyMap(oldValue.Interface()) if err != nil { return nil, err } newValue = reflect.ValueOf(copiedVal) }else{ newValue = oldValue } newMap.SetMapIndex(key, newValue) } } return newMap.Interface(), nil } ``` 此函数接受任意类型map 并返回其深层副本。需要注意的是,该方案虽然灵活但效率较低,在性能敏感的应用场景下不推荐使用。 #### 方法二:基于具体类型的编码/解码方式 如果已知要操作的具体类型,可以通过序列化(如 JSON、XML 等)再反序列化的手段达到目的。这种方式简单易懂且易于维护,缺点是在某些情况下可能会丢失原始数据中的零值字段信息[^2]。 ```go package main import ( "encoding/json" "fmt" ) type Person struct { Name string Age int } // 假设有一个存储Person对象的map originalMap := make(map[string]*Person) originalMap["Alice"] = &Person{Name: "Alice", Age: 30} originalMap["Bob"] = &Person{Name: "Bob", Age: 25} jsonData, _ := json.Marshal(originalMap) var clonedMap map[string]*Person json.Unmarshal(jsonData, &clonedMap) fmt.Printf("%v\n", originalMap) fmt.Printf("%v\n", clonedMap) ``` 上述例子展示了如何借助JSON编解码器完成对含有指针成员变量的地图进行深度克隆的过程。 #### 方法三:手动遍历构建新的映射表 最直观的方法就是显式迭代原 map 的每一个条目,并将其逐一加入到一个新的实例中去。这样做不仅清晰明了而且能够很好地控制哪些部分真正被复制下来以及怎样做才是合适的。 ```go package main import "fmt" type Employee struct { ID int Name string } employees := map[int]*Employee{ 1: {"张三"}, 2: {"李四"}, } copiedEmployees := make(map[int]*Employee, len(employees)) for id, emp := range employees { copiedEmp := *emp // 创建员工记录的一个独立副本来避免共享内存区域 copiedEmployees[id] = &copiedEmp } fmt.Println("Original:", employees) fmt.Println("Copied :", copiedEmployees) ``` 以上三种策略各有优劣,开发者应当依据实际需求选取最合适的一种。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值