成对键值的 map


许多语言中都会有的成对键值数据结构,在 Go 中是以内置类型map来实现,格式为map[keyType]valueType

创建与初始化 map

想要创建例一个map实例,但尚无任何键值对,可以使用make函数,例如:

package main

import "fmt"

func main() {
    passwords := make(map[string]int)
    fmt.Println(passwords)      // map[]
    fmt.Println(len(passwords)) // 0

    passwords["caterpillar"] = 123456
    passwords["monica"] = 54321
    fmt.Println(passwords)                // map[caterpillar:123456 monica:54321]
    fmt.Println(len(passwords))           // 2
    fmt.Println(passwords["caterpillar"]) // 123456
    fmt.Println(passwords["monica"])      // 54321
}

在上例中,passwords是个参考,指向make(map[string]int)创建的 map 实例。

类似一些语言(例如 Python),要设定一个键值对应时,使用[]=指定,要获取键对应的值时,使用[]指定键,这会返回对应的值,想知道map中的键数,可以使用len函数。

要注意的是,可用来做为键的值,必须是comparable,就目前来说,你要先知道的 comparable 类型有布尔、数字、字符串、指针(pointer)、channel、interface、struct,或者含有这这些类型的数组,这些是都可以使用==来比较的值;而 slice、map 与函数,就不能用来做为键。

如果已知 map 中会有的键值对,则可以如下创建 map:

package main

import "fmt"

func main() {
    passwords := map[string]int{
        "caterpillar": 123456,
        "monica":      54321,
    }

    fmt.Println(passwords)                // map[monica:54321 caterpillar:123456]
    fmt.Println(len(passwords))           // 2
    fmt.Println(passwords["caterpillar"]) // 12345
    fmt.Println(passwords["monica"])      // 54321
}

如果passwords创建时,最后一个键值项目后不换行,那么最后一个逗号就不需要,例如:

passwords := map[string]int {"caterpillar" : 123456, "monica" : 54321}

实际上,你也可以写passwords := map[string]int {},来创建一个没有任何键值对的map,这相当于写passwords := make(map[string]int),不过,若是var passwords map[string]int的话,只是创建一个参考名称passwords,默认空值是nil,也就是相当于var passwords map[string]int = nil的意思。

也就是说,var passwords map[string]int定义了一个参考类型,两个map类型的名称,可以指向同一个map实例,透过其中一个名称来改变map内容,从另一个名称就可以获得对应的修改:

package main

import "fmt"

func main() {
    passwds1 := map[string]int{"caterpillar": 123456}
    passwds2 := passwds1

    fmt.Println(passwds1) // map[caterpillar:123456]

    passwds2["monica"] = 54321
    fmt.Println(passwds1) // map[monica:54321 caterpillar:123456]
}

键值访问、删除

如方才所看到的,要设定一个键值对应时,使用[]=指定,要获取键对应的值时,使用[]指定键,这会返回对应的值,如果指定的键不存在,那么会返回值类型对应的空值,例如,若passwords := map[string]int {"caterpillar" : 123456},那么passwords["monica"]会返回 0。

不过,更精确地说,使用mapName[key]时,会返回两个值(Go 中允许同时返回多值),第一个是键对应的值,若没有该键就返回值类型的空值,第二个是布尔值,指出键是否存在。例如:

package main

import "fmt"

func main() {
    passwds := map[string]int{"caterpillar": 123456}

    v, exists := passwds["monica"]
    fmt.Printf("%d %t\n", v, exists) // 0 false

    passwds["monica"] = 54321
    v, exists = passwds["monica"]
    fmt.Printf("%d %t\n", v, exists) // 54321 true
}

因此,若只是单纯想测试键是否存在,只要用下划线_忽略返回的值就可以了,例如:

package main

import "fmt"

func main() {
    passwds := map[string]int{"caterpillar": 123456}
    name := "caterpillar"
    _, exists := passwds[name]
    if exists {
        fmt.Printf("%s's password is %d\n", name, passwds[name])
    } else {
        fmt.Printf("No password for %s\n", name)
    }
}

exists的指定与if的判断也可以写在同一行:

if _, exists := passwds[name]; exists {
    fmt.Printf("%s's password is %d\n", name, passwds[name])
} else {
    fmt.Printf("No password for %s\n", name)
}

如果想删除某个键值,可以使用delete函数,例如delete(passwds, "caterpillar")

迭代键值

如果要迭代map的键值,可以使用for range,例如:

package main

import "fmt"

func main() {
    passwords := map[string]int{
        "caterpillar": 123456,
        "monica":      54321,
    }

    for name, passwd := range passwords {
        fmt.Printf("%s : %d\n", name, passwd)
    }
}

如果只是想迭代map的键,可以如下:

package main

import "fmt"

func main() {
    passwords := map[string]int{
        "caterpillar": 123456,
        "monica":      54321,
    }

    for name := range passwords {
        fmt.Printf("%s\n", name)
    }
}

如果只想迭代map的值,可以如下:

package main

import "fmt"

func main() {
    passwords := map[string]int{
        "caterpillar": 123456,
        "monica":      54321,
    }

    for _, passwd := range passwords {
        fmt.Printf("%d\n", passwd)
    }
}

如果想获取map中的键清单或者是值清单,方式之一是使用 slice 进行收集,例如:

package main

import "fmt"

func keys(m map[string]int) []string {
    ks := make([]string, 0, len(m))
    for k := range m {
        ks = append(ks, k)
    }
    return ks
}

func values(m map[string]int) []int {
    vs := make([]int, 0, len(m))
    for _, v := range m {
        vs = append(vs, v)
    }
    return vs
}

func main() {
    passwords := map[string]int{
        "caterpillar": 123456,
        "monica":      54321,
    }

    fmt.Println(keys(passwords))   // [caterpillar monica]
    fmt.Println(values(passwords)) // [123456 54321]
}

Go 的map在迭代时没有一定的顺序,如果想要有排序结果,必须自行处理,例如,针对键排序来进行迭代:

package main

import "sort"
import "fmt"

func main() {
    passwords := map[string]int{
        "caterpillar": 123456,
        "monica":      54321,
        "hamimi":      13579,
    }

    keys := make([]string, 0, len(passwords))
    for key := range passwords {
        keys = append(keys, key)
    }
    sort.Strings(keys)

    for _, key := range keys {
        fmt.Printf("%s : %d\n", key, passwords[key])
    }
}




展开阅读全文