Maps
Go map是由键值对组成的无序集合,它的容量只受机器的内存限制. Key必须是唯一的并且它的类型,必须支持 == 和 != 操作符, 所以大多数内置类型可以被用于key(e.g., int, float64, rune ,string,可比较的数组以及structs, 基于内置类型的自定义类型, 以及指针). Slices 以及不能比较的数组和struct(它们的元素都不支持 == 和 !=), 或者自定义类型是基于这些不能比较的类型, 则都不能作为map的key。 指针,引用内型,以及其它的任意内置类型都可以作为 map的值(values). 同样map类型也可以做为另一个map的值。 所以创建复杂的数据结构非常简单。 Go map操作,如表4.3所示.
Maps是引用类型,所以传递的时候是廉价的(e.g., 64位为8bytes, 32位为4bytes). 而不管它保存多少的数据。Map的查询是非常快的——对比于线性搜索来说快非常多——但对于slice或者array直接索引来说慢两个数量级(e.g., 100 times). 在需要使用到map的地方,这依然非常的快的(实际操作,性能未必是一个问题).
由于slices不能用于map的key. 因此我们不能使用byte slice([]byte)用于map的key. 但是由于string([]byte)和[]byte(string)都不会改变底层的字节。 我们可以安全的将[]bytes转换为strings. 然后用于map的key, 对于之后需要使用到[]bytes,我们在转换回来。
一个map的key必须都是同一类型,但key和value的类型可以是不同的。至于map 的value使用,跟slice中的元素使用类似,在这里并没有过多的限制. 这也就是为什么value的类型可以是一个interface. 我们可以存储任意的类型, 只需它们都提供了指定接品的方法. 我们甚至可以声明map value类型为 empty interface , interface{}. 这样我们就可以存储任意类型的值。 但在访部这些值时, 我们需要检测类型, 或者类型转换
Table 4.3 Map Operations
Syntax | Description/result |
---|---|
m[k] = v | Assigns value v to map m under key k; if k is already in the map its previous value is discarded |
delete(m, k) | Deletes key k and its associated value from map m, or safely does nothing |
v := m[k] | Retrieves the value that corresponds to map m’s key k and assigns it to v; or assigns the zero value for the value’s type to v, if k isn’t in the ma |
v, found := m[k] | Retrieves the value that corresponds to map m’s key k and assigns it to v and true to found; or assigns the zero value for the value’s type to v and false to found, if k isn’t in the map |
len(m) | The number of items (key–value pairs) in map m |
Map创建语法
make(map[KeyType]ValueType, initialCapacity)
make(map[KeyType]ValueType)
map[KeyType]ValueType{}
map[KeyType]ValueType{key1: value1, key2: value2, ..., keyN: valueN}
内置的make()函数可以用于创建slices, maps, and channels. 当我们使用它来创建一个map. 它将创建一个空的map, 如果指定了可选的initialCapacity, 则map 指定了元素的数量。 如果超过了initialCapacity, map将会自动的增加内存, 以适应新的items. 第二种语法和第三种语法是相等的效果。
Creating and Populating Maps
以下这个例子展示了如何创建一个Maps, 并为它填充数据
massForPlanet := make(map[string]float64) // Same as: map[string]float64{}
massForPlanet["Mercury"] = 0.06
massForPlanet["Venus"] = 0.82
massForPlanet["Earth"] = 1.00
massForPlanet["Mars"] = 0.11
fmt.Println(massForPlanet)
//map[Venus:0.82 Mars:0.11 Earth:1 Mercury:0.06]
对于小的map来说,我们并不需要关心初始容量,但对于大的maps来说,这样可以改进性能。所以在知道容量(或者大约知道有多少数据时),最好给出initial capacity.
跟数组和slice一样, maps 也可以使用[]index操作符。但对于map来说,[]中的值不int, 比如这里的是字符串
将一个map输出到终羰,我们可以使用fmt.Println()函数。 它使用了%v格式,并且输出以空格分隔的key:value. Maps是没有排序的。所以在不同的机器上,上面的输出顺序可能不同。
我们之前有提到过指针也可以做为map 的key.
type Point struct{ x, y, z int }
func (point Point) String() string {
return fmt.Sprintf("(%d,%d,%d)", point.x, point.y, point.z)
}
triangle := make(map[*Point]string, 3)
triangle[&Point{89, 47, 27}] = "α"
triangle[&Point{86, 65, 86}] = "β"
triangle[&Point{7, 44, 45}] = "γ"
fmt.Println(triangle)
//map[(7,44,45):γ (89,47,27):α (86,65,86):β]
Point类型存储了3个int. 它还有一个String()方法,它可以保证Go在打印一个*Point类型时,可以调用这个String(), 而不是简单的输出Point的内存指针地址。
顺但说一个,我们可以使用%p格式来输出内存地址。
在上面的这段的代码中,我们创建了一个初始容量的map, 并且填充了三个指针类型的key和字符串类型的值。每一个Point是通过composite literal语法创建的,并且使用了&操作符。所以我们获得的是一个*Point 而不是Point的值。 由于有Point.String()方法,所以我们可以输出可读的*Point的值。
但使用指针会有一个问题,如果我们提供了两个相同的坐标点,但是坐标是分开创建的(拥有不同的地址), 那么就会导致,相同的坐标,却对应不同的值(key不是唯一). 在这里,我们可以不用使用指针,而是直接将Pointer作为key的值。因为Go也是支持struct做为keys的值,只要它所有的段,都是可以通过 == 和 != 进行比较.
nameForPoint := make(map[Point]string) // Same as: map[Point]string{}
nameForPoint[Point{54, 91, 78}] = "x"
nameForPoint[Point{54, 158, 89}] = "y"
fmt.Println(nameForPoint)
以下是一个遍历map的例子
populationForCity := map[string]int{"Istanbul": 12610000,
"Karachi": 10620000, "Mumbai": 12690000, "Shanghai": 13680000}
for city, population := range populationForCity {
fmt.Printf("%-10s %8d\n", city, population)
}
/*
Shanghai 13680000
Mumbai 12690000
Istanbul 12610000
Karachi 10620000
*/
我们使用了composite literal语法创建了一个map. 当使用for ... range loop遍历一个map里,它里出现了两个变量,用于保存每次遍历返回的key和value. 如果仅有一个变量,则每次返回的是key.
Maps Lookups
Go提供了两种相似的语法用于map的查找,都是使用[]索引操作符。
population := populationForCity["Mumbai"]
fmt.Println("Mumbai's population is", population)
population = populationForCity["Emerald City"]
fmt.Println("Emerald City's population is", population)
/*
Mumbai's population is 12690000
Emerald City's population is 0
*/
如果要查找的key存在于map, 则返回相应的值。 但如果key不存在,则返回map value类型的零值。 所以我们在这里不能够区分,"Emerald City"的值是0, 还是没有找到。 还好Go 提供了第二种找到方法,来解决这个问题。
city := "Istanbul"
if population, found := populationForCity[city]; found {
fmt.Printf("%s's population is %d\n", city, population)
} else {
fmt.Printf("%s's population data is unavailable\n", city)
}
city = "Emerald City"
_, present := populationForCity[city]
fmt.Printf("%q is in the map == %t\n", city, present)
/*
Istanbul's population is 12610000
"Emerald City" is in the map == false
*/
如果我们提供两个变量用于保存map的[]索引操作符的返回值。 则第一个将获得key对应的值(或者是如果不存在,则返回零值). 第二次则是true(或者是这个key不存在,则为false). 这就允许我们确认一个key是否存在于map. 如果我们仅是想要知道一个key是否存在于map, 则可以使用blank identifier。
Modifying Maps
Map 的元素是一对键值对, 以下是一些示例,用来说明,添加,删除,修改map中的键值对
fmt.Println(len(populationForCity), populationForCity)
delete(populationForCity, "Shanghai") // Delete
fmt.Println(len(populationForCity), populationForCity)
populationForCity["Karachi"] = 11620000 // Update
fmt.Println(len(populationForCity), populationForCity)
populationForCity["Beijing"] = 11290000 // Insert
fmt.Println(len(populationForCity), populationForCity)
/*
4 map[Shanghai:13680000 Mumbai:12690000 Istanbul:12610000 Karachi:10620000]
3 map[Mumbai:12690000 Istanbul:12610000 Karachi:10620000]
3 map[Mumbai:12690000 Istanbul:12610000 Karachi:11620000]
4 map[Mumbai:12690000 Istanbul:12610000 Karachi:11620000 Beijing:11290000]
*/
插入和更新map的元素的语法是相同的:如果给定的key的元素不存在,则添加一个新的元素。 如果存在,则更新这个元素,同时原来的值被舍弃。而如果我们要删除的元素不存在于map, 则什么也不做。
我们不可以对key进行直接修改,但可以通过以下的方法, 来完成对key的修改。
oldKey, newKey := "Beijing", "Tokyo"
value := populationForCity[oldKey]
delete(populationForCity, oldKey)
populationForCity[newKey] = value
fmt.Println(len(populationForCity), populationForCity)
//4 map[Mumbai:12690000 Istanbul:12610000 Karachi:11620000 Tokyo:11290000]
Key-Orderd Map Iteration
为了方便阅读,我们需要对map的键进行排序,以下这个方法是按照字母的顺序,排序city.
cities := make([]string, 0, len(populationForCity))
for city := range populationForCity {
cities = append(cities, city)
}
sort.Strings(cities)
for _, city := range cities {
fmt.Printf("%-10s %8d\n", city, populationForCity[city])
}
/*
Beijing 11290000
Istanbul 12610000
Karachi 11620000
Mumbai 12690000
*/
Map Inversion
我们可以互换key和value, 但value必须是唯一的
cityForPopulation := make(map[int]string, len(populationForCity))
for city, population := range populationForCity {
cityForPopulation[population] = city
}
fmt.Println(cityForPopulation)
//map[12610000:Istanbul 11290000:Beijing 12690000:Mumbai 11620000:Karachi]