13.Go语言基础之map

Go语言中提供的映射关系容器为map,其内部使用散列表(hash)实现。

一、map

map是一种无序的基于key-value的数据结构,类似于python中的字典。Go语言中的map是引用类型,必须初始化才能使用。

二、map定义

Go语言中 map的定义语法如下:

map[KeyType]ValueType

其中,

  • KeyType:表示键的类型。
  • ValueType:表示键对应的值的类型。

map类型的变量默认初始值为nil,需要使用make()函数来分配内存。语法为:

make(map[KeyType]ValueType, [cap])

其中cap表示map的容量,该参数虽然不是必须的,但是我们应该在初始化map的时候就为其指定一个合适的容量。

注意:定义map,key值必须是可Hash,用的最多的是数字和字符串

三、map基本使用

map中的数据都是成对出现的,map的基本使用示例代码如下:

func main() {
	scoreMap := make(map[string]int, 8)
	scoreMap["张三"] = 90
	scoreMap["小明"] = 100
	fmt.Println(scoreMap)
	fmt.Println(scoreMap["小明"])
	fmt.Printf("type of a:%T\n", scoreMap)
}

输出:

map[小明:100 张三:90]
100
type of a:map[string]int

map也支持在声明的时候填充元素,例如

func main() {
	userInfo := map[string]string{
		"username": "白雪公主",
		"password": "123456",
	}
	fmt.Println(userInfo) //map[password:123456 username:白雪公主]
}

简单示例:

//map(映射)
func main() {
	// 光声明map类型,但是没有初始化
	var a map[string]int
	fmt.Println(a == nil)
	// map的初始化
	a = make(map[string]int, 8)
	fmt.Println(a == nil)
	// map中添加键值对
	a["小王子"] = 100
	a["童话"] = 200
	fmt.Printf("a:%#v\n", a)
	fmt.Printf("type:%T\n", a)
	// 声明map同时完成初始化
	b := map[int]bool{
		1: true,
		2: false,
	}
	fmt.Printf("b:%#v\n", b)
	fmt.Printf("type:%T", b)
	var c map[int]int
	c[100] = 200 //c 这个map没有初始化不能直接操作
	fmt.Println(c)
}

四、判断某个键是否存在

Go语言中有个判断map中键是否存在的特殊写法,格式如下:

value, ok := map[key]

举个栗子:

func main() {
	// 判断某个键存不存在
	var scoreMap = make(map[string]int)
	scoreMap["李四"] = 90
	scoreMap["小明"] = 100
	scoreMap["李华"] = 60
	// 如果key存在ok为true,v为对应的值;不存在ok为false,v为值类型的零值
	//v, ok := scoreMap["张三"]
	_, ok := scoreMap["张三"]
	if ok {
		fmt.Println("张三在scoreMap中")
	} else {
		fmt.Println("查无此人")
	}
}

五、Map的遍历

Go语言中使用for range遍历map。

func main() {
	// for range遍历map
	scoreMap := make(map[string]int)
	scoreMap["张三"] = 90
	scoreMap["小明"] = 100
	scoreMap["娜扎"] = 60
	for k, v := range scoreMap {
		fmt.Println(k, v)
	}
}

如果我们只想遍历key 的时候,可以按下面的写法:

func main() {
	scoreMap := make(map[string]int)
	scoreMap["张三"] = 90
	scoreMap["小明"] = 100
	scoreMap["娜扎"] = 60
	// 只遍历Key 
	for k := range scoreMap {
		fmt.Println(k)
	}
}

注意:遍历map时的元素顺序与添加键值对的顺序无关。

六、使用delete()函数删除键值对

使用delete()内建函数从map中删除一组键值对,delete()函数的格式如下:

delete(map, key)

其中:

  • map:表示要删除键值对的map
  • key:表示要删除的键值对的键

示例代码如下:

func main() {
	// 使用delete()函数删除键值对
	scoreMap := make(map[string]int)
	scoreMap["张三"] = 90
	scoreMap["小明"] = 100
	scoreMap["娜扎"] = 60
	delete(scoreMap, "小明") //将小明:100从map中删除
	delete(scoreMap, "栗子") //如果存在就删除,不存在就不操作,不报错
	for k, v := range scoreMap {
		fmt.Println(k, v)
	}
}

输出结果如下:

张三 90
娜扎 60

七、按照指定顺序遍历map

func main() {
	rand.Seed(time.Now().UnixNano()) //初始化随机数种子

	var scoreMap = make(map[string]int, 200)
	// 添加100个键值对
	for i := 0; i < 100; i++ {
		key := fmt.Sprintf("stu%02d", i) //生成stu开头的字符串
		value := rand.Intn(100)          //生成0~99的随机整数
		scoreMap[key] = value
	}
	/*
		需求:按照key 从小到大的顺序去遍历scoreMap
			1.先取出所有的key存放到切片中
			2.对key做排序
			3.按照排序后的key对scoreMap排序
	*/
	//1.取出map中的所有key存入切片keys
	var keys = make([]string, 0, 200)
	for key := range scoreMap {
		keys = append(keys, key)
	}
	//2.对切片进行排序
	sort.Strings(keys) // keys目前是一个有序的切片
	//3.按照排序后的key遍历map
	for _, key := range keys {
		fmt.Println(key, scoreMap[key])
	}
}

八、元素为map类型的切片

下面的代码演示了切片中的元素为map类型时的操作:

func main() {
	// 元素类型为map 的切片
	var mapSlice = make([]map[string]int, 3, 3) // 只完成了切片的初始化
	// [nil,nil,nil]
	fmt.Println(mapSlice[0] == nil)
	// 还需要完成内部元素的初始化
	mapSlice[0] = make(map[string]int, 3)
	mapSlice[0]["沙河小王子"]= 100
	fmt.Println(mapSlice)
}

九、值为切片类型的map

下面的代码演示了map中值为切片类型的操作:

func main() {
	var sliceMap = make(map[string][]string, 3)
	fmt.Println(sliceMap)
	fmt.Println("after init")
	key := "中国"
	value, ok := sliceMap[key]
	if !ok {
		value = make([]string, 0, 2)
	}
	value = append(value, "北京", "上海")
	sliceMap[key] = value
	fmt.Println(sliceMap)
}

十、Map的相等性

map 之间不能使用 == 操作符判断,== 只能用来检查 map 是否为 nil

func main() {
	map1 := map[string]int{
		"one": 1,
		"two": 2,
	}
	map2 := map1
    if map1 ==nil{
    	fmt.Println("map1为空")
	}else {
		fmt.Println("map1不为空")
	}
	if map1 == map2 { // 直接报错,不能直接比较
	}
	
}

十一、Map 是引用类型

和 [slices]类似,map 也是引用类型当 map 被赋值为一个新变量的时候,它们指向同一个内部数据结构。因此,当改变其中一个变量,就会影响到另一变量

func main() {
	personSalary := map[string]int{
		"steve": 12000,
		"jamie": 15000,
	}
	personSalary["mike"] = 9000
	fmt.Println("Original person salary", personSalary)
	newPersonSalary := personSalary
	newPersonSalary["mike"] = 18000
	fmt.Println("Person salary changed", personSalary)

}

上面程序中的第 14 行,personSalary 被赋值给 newPersonSalary。下一行 ,newPersonSalarymike 的薪资变成了 18000personSalaryMike 的薪资也会变成 18000。程序输出:

Original person salary map[jamie:15000 mike:9000 steve:12000]
Person salary changed map[jamie:15000 mike:18000 steve:12000]

当 map 作为函数参数传递时也会发生同样的情况。函数中对 map 的任何修改,对于外部的调用都是可见的

十二、小练习

  1. 写一个程序,统计一个字符串中每个单词出现的次数。比如:”how do you do”中how=1 do=2 you=1

    func main() {
    	// 统计一个字符串中每个单词出现的次数
    	// "how do you do"中每个单词出现的次数
    	// 0. 定义一个map[string]int
    	var s = "how do you do"
    	var wordCount = make(map[string]int, 10)
    	// 1. 字符串中都有哪些单词
    	words := strings.Split(s, " ")
    	// 2. 遍历单词做统计
    	for _, word := range words {
    		v, ok := wordCount[word]
    		if ok {
    			// map中有这个单词的统计记录
    			wordCount[word] = v + 1
    		} else {
    			// map中没有这个单词的统计记录
    			wordCount[word] = 1
    		}
    	}
    	for k, v := range wordCount {
    		fmt.Println(k, v)
    	}
    }
    
  2. 观察下面代码,写出最终的打印结果

    func main() {
    	type Map map[string][]int
    	m := make(Map)
    	s := []int{1, 2}
    	s = append(s, 3)
    	fmt.Printf("%+v\n", s)
    	m["Babyboy"] = s
    	s = append(s[:1], s[2:]...)
    	fmt.Printf("%+v\n", s)
    	fmt.Printf("%+v\n", m["Babyboy"])
    }
    /*
    [1 2 3]
    [1 3]
    [1 3 3]
    */