网站首页 文章专栏 golang 中 map 初始化和正确的使用方法(最新的正确使用姿势)
golang 中 map 初始化和正确的使用方法(最新的正确使用姿势)
编辑时间:2017年12月26日 14:52 作者:赵彦昌 浏览量:14403

翻译官方文档https://blog.golang.org/go-maps-in-action

介绍

 计算机科学中最有用的数据结构之一就是哈希表。 许多散列表实现存在不同的属性,但通常它们提供快速查找,添加和删除。Go提供了一个内置的map 类型,去实现一个哈希表。


声明与初始化

声明 一个 map 类型像这样:

map[keyType]valueType
键(key)的类型可以是任意可比较的类型,值(value)的类型可以是任意类型,甚至是另一个 map 类型


下面的变量 m 是一个字符串键到int值的映射:

var m map[string]int

Map类型是引用类型,像指针或切片一样,所以上面的m值是nil; 它没有指向一个初始化的map。 当读取像上面声明的空map时返回为nil,但试图写入上面这样的空map 时将导致运行错误; 不要这样做。 要先初始化map,请使用内置的make函数:

m = make(map[string]int)

make函数将分配并初始化一个散列 map 数据结构,并返回指向它的指针。该数据结构的细节是运行时的实现细节,并不是由语言本身指定的。 在本文中,我们将重点介绍地图的使用,而不是它们的实现。


使用 map

Go 提供了一种大家熟悉的语法去使用 map.  下面语句将设置 字符串 为"route"的key 映射 到值为66 的value:



 

m["route"] = 66

该语句检索key 为 "route" 的值, 并将它赋值给新的变量 i :

i  :=  m["route"]


如果请求的 key 不存在,我们将返回value Type  的空值。例子中情况下,值类型为 int, 因此值为 0:

j := m["root"]

//  j == 0


使用内置的 len 函数返回map 的长度(多少个已赋值的 key )

n := len(m)


使用内置的delete 函数来删除  map 的一条映射:

delete(m, "route")

fmt.Println(len(m))

// 0

当你删除一个不存在的 key 时,它不会做执行任何操作( 不会报错)


一个有两个的返回值用来测试 是否 存在该key:

i, ok  := m["route"]

在这个语句中,第一个值 i 被分配到 key 为“route”映射的值。如果该 key 不存在,返回值类型的空值. 第二个值 ok 是一个 bool 类型,如果该 key 存在于map中,则为 true, 如果没有,则为false。


如果你要在不检索值的情况下,测试一个 key , 可以用 下划线 代替第一个变量:

_, ok  := m["route"]


遍历一个 map:

for key, value := range m {

            fmt.Println("key:", key, "value:", value)

}


使用一些数据初始化数据:

commits := map[string]int{

            "rsc": 3711,

            "r":      2148,

            "gri":  1908,

            "adg":  912,

}


相同的语法可以用来初始化一个空映射, 它在功能上与使用 make 函数一样:

m = map[string]int{}


利用空值

    下面将告诉你, map 针对不存在的key 返回的空值,很好用。

例如,布尔值的映射可以用作一组集合类的数据结构(请记住,布尔类型的空值是false)。此示例遍历节点的链接列表并打印其值。它使用一个 map 节点指针来检测列表中的循环。

        type Node struct {

                Next    *Node

                Value    interface{}

        }

        var first *Node

        visited := make(map[*Node]bool)

        for n := first; n != nil;  n = n.Next {

                if vistied[n] {

                        fmt.Println("cycle detected")

                        break

                }

                visited[n] = true

                fmt.Println(n.Value)

        }

如果 n 已被访问, 则表达式 visited[n] 为真, 如果 n 不存在则为 false. 没有必要使用双值形式来测试 map 中是否存在n; 空值默认已为我们做了。


另一个有用的零值的例子是一张切片 map。因为它是一个又一个将值追加到一张切片图上的一条直线;没有必要检查键是否存在。在下面的示例中,切片填充了人的值。每个人都有一个名字和一个喜欢列表。该示例创建一个 map,将每个喜好与喜欢它的人相关联。

        type Person struct {

                Name string

                Likes []string

        }


        likes := make(map[string][]*Person)

        for _, p := range people {

                for _, l := range p.Likes {

                        likes[l] = append(likes[l], p)

                }

        }


打印一份喜欢奶酪的人:

        for _, p := range likes["cheese"] {

                fmt.Println(p.Name, " likes cheese.")

        }


打印一份喜欢培根的人的数量:

        fmt.Println(len(likes["bacon"]), "people like bacon.")


假如没有喜欢培根和奶酪,换句话说,即使最后两个例子返回 都是空值,但它们是不会报错的。


Key 类型

    正如前面所述, map 的 key 可以是任意可比较的类型,GO语言规范定义了  布尔值,数字, 字符串, 指针、通道和接口类型和结构体或前面所说的类型的数组。不能成为key 的类型为: 切片、map 、函数; 这些类型不能用 == 比较。


很明显,字符串,整数,和其他基本类型可以利用地图的钥匙,但意想不到的是结构体。结构体可用于多维键数据。例如,下面 这个 map 可以用来计算各国的网页点击率:

hits := make(map[string]map[string]int)

这个map key 为网页路径 value 为另一个map , key string 为城市缩写,value int 为点击次数, 比如获取 读取文档页面的郑州点击数量:

n := hits["/doc/"]["zz"]    // zhengzhou


不幸的是,这种方法在添加数据时变得笨拙,对于任何给定的外部键,您必须检查内部映射是否存在,并在需要时创建它:

func add(m map[string]map[string]int, path, country string) {

        mm, ok := m[path]

        if !ok {

                mm = make(map[string]int)

                m[path] = mm

        }

        mm[country]++

}

add(hits, "/doc/", "zz")


另外,善于使用结构体的key 会减少很多复杂性:

        type Key struct {

                    Path, Country string

}

hits := make(map[Key]int)


像这样使用, 一行代码足已:

hits[Key{"/doc/", "zz"}]++


同样简单的是,如要看有多少郑州人读过这篇文件:

n := hits[Key{"/doc/", "zz"}]


并发

    Map 对于 并发使用是不安全的:它并没有定义同时 读写时会发生什么 。如果 你需要读写在并发中使用时,必须被某种同步机制约束。为了保护Map, 常用的方式是 sync.rwmutex

下面这条语句声明 一个计数器变量,包含Map 和 一个嵌入的 sync.rwmutex 的匿名结构体。

    var counter = struct {

            sync.RWMutex

            m map[string]int

    }{m: make(map[string]int)}


当读取时,使用读锁:

   counter.RLock()

    n := counter.m["some_key"]

   counter.RUnlock()

   fmt.Println("some_key:", n)


当写时,使用写锁:

    counter.Lock()

     counter.m["some_key"]++

    counter.Unlock()



    Map  的 key 在迭代时,是无顺的,也就是说,在两次迭代同一个map 时,不能保证key 出现的顺序。下面例子可以帮你维护一个有顺的map:

import "sort"


var m map[int]string

var keys []int

for k := range m {

        keys = append(keys, k)

}

sort.Ints(keys)

for _, k := range keys {

        fmt.Println("Key:", k, "value:", m[k])

}


结束,有不懂的,请留言

一个下午才整理出来











来说两句吧
最新评论