容器
3.3 映射(map)——建立事物关联的容器
Go 语言提供的映射关系容器为 map。map 使用散列表( hash )实现。
3.3.1 添加关联到map并访问关联和数据
Go语言中map的定义是这样的:
map[keyType]ValueType
- KeyType为键类型
- ValueType是键对应的值类型
一个map里, 符合KeyType和ValueType的映射总是成对出现。
下面的代码展示了map的基本使用环境。
scene := make(map[string]int)
scene["route"] = 66
fmt.Println(scene["route"]) //输出66
v := scene["route2"]
fmt.Println(v) //输出0
某些情况下 需要明确知道查询中某个键是否在 map 中存在 ,可以使用一种特殊的写法来实现,看下面的代码:
v , ok := scene [” route ” ]
在默认获取键值的基础上,多取了一个变量 ok 可以判断键 route 是否存在于 map 中。
还有一种在声明时填充内容的方式,代码如下
m := map[string]string{
"W": "forward",
"A": "left",
"D": "right",
"S": "backward",
}
例子中并没有使用 make 而是使用大括号进行内容定义,就像 JSON 格式一样,冒号的左边是 key ,右边是值,键值对之间使用逗号分隔。
3.2.2 遍历map的"键值对"
map的遍历过程使用for range 循环完成,代码如下:
scene := make(map[string]int)
scene["route"] = 66
scene["route2"] = 4
scene["route3"] = 666
for k, v := range scene {
fmt.Println(k, v)
}
//如只遍历值,将不需要的键改为匿名遍历形式,
//只遍历键时,使用下面的形式:
for k := range scene {
fmt.Println(k)
}
注意: 遍历输出的元素顺序与填充顺序无关,不能期望map在遍历时返回某种期望顺序的结果。如果需要特定排序的遍历结果,正确的做法时排序。
3.3.3 使用delete()函数从map中删除键值对
delete()函数的格式如下:
delete(map, 键)
示例如下:
scene := make(map[string]int)
scene["route"] = 66
scene["route2"] = 4
scene["route3"] = 666
delete(scene, "route2")
for k, v := range scene {
fmt.Println(k, v)
}
//代码输出如下:
//route 66
//route3 666
3.3.4 清空map中的所有元素
清空 map的唯一办法就是重新 make 一个新的 map 。不用担心垃圾回收的效率,Go 语言中的并行垃圾回收效率比写一个清空函数高效多了。
3.3.5 并发环境中使用的map—sync.Map
Go语言中的map在并发情况下,只读是线程安全的,同时读写线程不安全。
需要并发读写时,一般的方法是加锁,但这样的性能并不高。Go语言在1.9版本中提供了一种效率较高的并发安全sync.Map。sync.Map和map不同,不是以语言原生形态提供,而是在sync包下的特殊结构。
sync.Map有以下特性:
- 无需初始化,直接声明即可。
- 不能使用map的方式进行取值和设置操作,而是使用方法调用,Store表示存储,Load表示获取,Delete表示删除。
- 使用Range配合一个回调函数进行遍历操作,通过回调返回内部遍历的值。Range参数中的回调函数返回值功能是:需要继续迭代遍历时,返回true; 终止迭代遍历时,返回false。
package main
import (
"fmt"
"sync"
)
func main() {
var scene sync.Map
//将键值对保存到sync.Map中
scene.Store("greece", 96)
scene.Store("london", 100)
scene.Store("egypt", 200)
//从sync.Map中根据键提取值
fmt.Println(scene.Load("london"))
//根据键删除对应的键值对
scene.Delete("london")
//遍历所有sync.Map中的键值对
scene.Range(func(k, v interface{}) bool {
fmt.Println("iterator: ", k, v)
return true
})
}
sync.Map 没有提供获取 map 量的方法,替代方法是获取时遍历自行计算数量。 sync.Map为了保证并发安全有一些性能损失,因此在非并发情况下,使用 map 相比使用 sync.Map会有更好的性能。
3.4 列表(list)
列表是一种非连续存储的容器,由一个节点组成,节点通过一些变量记录彼此之间的关系。列表有多种实现方法,如单链表、双链表等。
Go 语言中 将列表使用 container/list 包来实现,内部的实现原理是双链表。列表能够高效地进行任意位置的元素插入和删除操作。
3.4.1 初始列表
list的初始化有两种方法:New和声明。两种方法的初始化效果是一致的。
-
New方法初始化list
变量名 := list.New()
-
声明初始化list
var 变量名 list.List
3.4.2 在列表中插入元素
双链表支持从队列前方或后方插入元素,分别对应的方法是 PushFront和PushBack。
提示: 这两个方法都会返回一个*list.Element 结构。如果在以后的使用中需要删除插入的元素,则只能通过***list. Element 配合 Remove()方法进行删除, 可以让删除更加效率化**。
代码示例如下:
l := list.New()
l.PushBack("76")
l.PushFront("first")
列表插入元素的方法如下表:
方法 | 功能 |
---|---|
InsertAfter(v interface{}, mark *list.Element) *Element | 在mark点之后插入元素,mark点由其他插入函数提供 |
InsertBefore(v interface{}, mark *list.Element) *Element | 在mark点之前插入元素,mark点由其他插入函数提供 |
PushBackList(other *List) | 添加other列表元素到尾部 |
PushFrontList(other *List) | 添加other列表元素到头部 |
3.4.3 从列表中删除元素
列表的插入函数的返回值会提供一个*list.Element结构,这个结构记录这列表元素的值和其他节点之间的关联关系。从列表中删除元素时,需要用到这个结构进行快速删除。
package main
import "container/list"
func main() {
l := list.New()
l.PushBack("container")
l.PushFront(76)
ele := l.PushBack("name") //尾部添加元素后保存句柄
l.InsertBefore("before", ele) //在name之前添加before
l.InsertAfter("after", ele) //在name之后添加after
l.Remove(ele) //删除元素
}