业余时间翻译,水平很差,如有瑕疵,纯属无能。

原文链接

http://blog.golang.org/go-maps-in-action

go语言中的map实战

1. 简介

哈希表是计算机科学中最重要的数据结构之一。许多哈希表的实现有着千差万别的特性,但是总体上他们都提供了快速查询,添加和删除功能。go语言提供了内置数据类型map。

2. 声明和初始化

map的声明格式如下:

map[KeyType] ValueType

KeyType类型必须是可以比较的,而ValueType可以是任意类型,甚至是另一个map。

以下这个m是一个键值为string,值为int的哈希表:

var m map[string]int

哈希表类型是引用类型,像指针或者切片m指向的值是nil;它没有指向一个初始化了的哈希表。一个nil哈希表在读的时候,像一个空的哈希表,但是尝试向m中写数据会引发一个运行时panic,所以别那样做。 使用内置函数make初始化一个哈希表:

m = make(map[string]int) 

make函数申请并初始化了一个哈希表的数据结构并且返回一个指向这个初始化好了的哈希表。 哈希表的数据结构是go本身运行时的一个实现细节,并没有被语言本身所规定【翻者补充:类似c++不同编译器如何实现虚函数一样吧】。 文章中只关心哈希表的使用而非实现。

3. 使用哈希表

go中的哈希表的使用方法和其他语言相似,向哈希表中插入一个键为“route”值为66的语句为:

m["route"] = 66

查询键为“route”并且把对应的值赋给新的变量i的语句为:

i := m["route"]

如果查询的键值在哈希表中不存在,将拿到值类型的“0”值。 以m为例,值类型为int,则“0”值为1:

j := m["root"]
// j == 0

len是返回哈希表中数据个数的内置函数:

n := len(m)

delete是删除哈希表中某一键值数据的内置函数:

delete(m, "route")

delete函数返回值为空,如果键值不存在则不做任何操作。

使用两个返回值的方式可以检查键值是否存在:

i, ok := m["route"]

在这个语句中,i被赋值为哈希表中键值为“route”的值。如果那个键值不存在,i被赋值为值类型中的“0”值。第二个返回值是布尔类型,如果是true,表明键值存在,否则不存在。

如果只是检查键值是否存在,则第一个返回值使用下划线“_":

_, ok := m["route"]

如果要遍历一个哈希表中的内容,使用range关键字:

for key, value := range m {
fmt.Println("Key:", key, "Value:", value)
}

如果要初始化数据,使用哈希表的字面表示:

commits := map[string]int{
"rsc": 3711,
"r": 2138,
"gri": 1908,
"adg": 912,
}

同样的语法可以初始化一个空的哈希表,这种用法达到的效果和make一致:

m = map[string]int{}

4. 利用“0”值 

在哈希表中查询数据,如果键值不存在,返回一个值类型的“0”值是很方便的:

It can be convenient that a map retrieval yields a zero value when the key is not present.

例如,一个布尔值的哈希表可以被用来当做一个set使用(布尔类型的“0”值是false)。下边这个例子遍历一个Node的链表并且打印他们的值,它使用了一个Node的指针为key的哈希表去判断链表中是否有环:

    type Node struct {
Next *Node
Value interface{}
}
var first *Node visited := make(map[*Node]bool)
for n := first; n != nil; n = n.Next {
if visited[n] {
fmt.Println("cycle detected")
break
}
visited[n] = true
fmt.Println(n.Value)
}

visited[n]表达式如果是真,表明n已经被访问过了,如果是假,表明还没有。 这样就不需要使用两个返回值的方式去检查n是否在map中真的存在;默认的“0”值帮我们做了。

另一个有用的例子是切片的哈希表。想一个空切片中添加数据会申请一个新的切片所以想一个切片的map中append数据会只是占用一行;不需要检查key是否存在。在下边的例子中,切片用被用来存放person类型的值,每个Person有一个Name字段和一个切片,这个例子中创建了一个哈希表关联每种物品和一个喜欢他的人的切片。【做了个倒排?】

    type Person struct {
Name string
Likes []string
}
var people []*Person likes := make(map[string][]*Person)
for _, p := range people {
for _, l := range p.Likes {
likes[l] = append(likes[l], p)
}
}

打印喜欢cheese的People:

    for _, p := range likes["cheese"] {
fmt.Println(p.Name, "likes cheese.")
}

打印出喜欢bacon的人数

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

注意,这里range函数和len函数都把nil切片看做一个长度为零的切片,即使没有人喜欢cheese或者bacon,也不会有问题。

5. 键的类型

像刚才提过的,键的类型必须是可比较的。go语言的spec中准确的定义了这个要求,简而言之,可以比较的类型包括:布尔,数字,字符串,指针,消息channel,接口类型和任何包含了以上类型的结构体和数组。不在此范围的类型包括切片,哈希表和函数;这些类型不能使用 “==” 做比较,也不能被用来做哈希表的键值。

很明显字符串,整型和其他基础类型可以作为哈希表的键。 意想不到的是结构体也可以作为键值,例如,这个哈希表的哈希表可以用来存放不同国家的访问数。

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

这是一个键为字符串,值为字符串到int的哈希表。最外边表的每一个键值是到达内部哈希表的路径,每个被嵌套的哈希表的键值是一个两个字母的国家码。这个表达式可以得到一个澳大利亚人访问文档页面的次数。

n := hits["/doc/"]["au"]

不幸的是这个方法在添加数据的时候很笨重,对每个键,需要判断嵌套的map是否存在,如果有必要的话需要创建:

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/", "au")

另一方面,使用结构体作为键的哈希表可以解除这种复杂性:

type Key struct {
Path, Country string
}
hits := make(map[Key]int)

当一个越南人访问主页的时候,一行就可以搞定:

hits[Key{"/", "vn"}]++

查看多少个瑞士人查看了spec页面的语句也很简单:

n := hits[Key{"/ref/spec", "ch"}]

6. 并发

哈希表在有并发的场景并不安全:同时读写一个哈希表的后果是不确定的。如果你需要使用goroutines同时对一个哈希表做读写,对哈希表的访问需要通过某种同步机制做协调。一个常用的方法是是使用 sync.RWMutex。

这个语句生命了一个counter变量,这是一个包含了一个map和sync.RWMutex的匿名结构体。

var counter = struct{
sync.RWMutex
m map[string]int
}{m: make(map[string]int)}

读counter前,获取读锁:

counter.RLock()
n := counter.m["some_key"]
counter.RUnlock()
fmt.Println("some_key:", n)

写counter前,获取写锁

counter.Lock()
counter.m["some_key"]++
counter.Unlock()

7. 遍历顺序

当使用range循环遍历一个哈希表的时候,遍历顺序是不保证稳定的。因为Go1版本将map的便利顺序随机化了,如果程序依赖之前实现中的稳定的便利顺序的话。【翻者注。。不知道怎么翻译】 如果你需要一个稳定的遍历顺序,你必须维护一个独立的数据结构用来保证这个顺序。下面这个例子使用了一个独立的排序切片,按照键值顺序打印一个 map[int] string数据:

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])
}

By Andrew Gerrand

【翻译】go语言中的map实战的更多相关文章

  1. Go语言中的map(十一)

    map是一种无序的基于 key-value 的数据结构,Go语言中的map是引用类型,所以跟切片一样需要初始化才能使用. 定义map 定义 map 的语法如下: map[keyType]ValueTy ...

  2. Go语言中的map

    map是一个集合,可以使用类似处理数组和切片的方式迭代map中的元素.但map是无序的集合.无序的原因是map的实现使用了散列表. map的创建并初始化主要是两种方式: 1.内置的make函数 2.使 ...

  3. 详细解读go语言中的map

    Map map底层是由哈希表实现的 Go使用链地址法来解决键冲突. map本质上是一个指针,指向hmap 这里的buckets就是桶,bmap 每一个bucket最多可以放8个键值对,但是为了让内存排 ...

  4. C语言中void*详解及应用

    void在英文中作为名词的解释为“空虚:空间:空隙”:而在C语言中,void被翻译为“无类型”,相应的void *为“无类型指针”.void似乎只有“注释”和限制程序的作用,当然,这里的“注释”不是为 ...

  5. Java 语言中 Enum 类型的使用介绍

    Enum 类型的介绍 枚举类型(Enumerated Type) 很早就出现在编程语言中,它被用来将一组类似的值包含到一种类型当中.而这种枚举类型的名称则会被定义成独一无二的类型描述符,在这一点上和常 ...

  6. 关于Javascript语言中this关键字(变量)的用法

    最近很多 Javascript初学者朋友总在问: Javascript 的this 关键字的用法.我在这里索性总结一下 this关键字的用法. this 关键字是面向对象编程语言中的一个重要概念!在J ...

  7. [转]理解Go语言中的nil

    最近在油管上面看了一个视频:Understanding nil,挺有意思,这篇文章就对视频做一个归纳总结,代码示例都是来自于视频. nil是什么 相信写过Golang的程序员对下面一段代码是非常非常熟 ...

  8. [转]Go语言中的make和new

    前言 本文主要给大家介绍了Go语言中函数new与make的使用和区别,关于Go语言中new和make是内建的两个函数,主要用来创建分配类型内存.在我们定义生成变量的时候,可能会觉得有点迷惑,其实他们的 ...

  9. 【转】C语言中,为什么字符串可以赋值给字符指针变量

    本文是通过几篇转帖的文章整理而成的,内容稍有修改: 一. C语言中,为什么字符串可以赋值给字符指针变量 char *p,a='5';p=&a;                     //显然 ...

随机推荐

  1. asp.net控件ControlToValidate同OnClientClick冲突解决办法

    <asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" Error ...

  2. network: 思科-华为光模块

    思科-华为光模块的分类比较   摘要:本文介绍:思科GLC-SX-MM,GLC-LH-SM光模块等产品参数与图片,华为光模块的型号与分类等知识. 光模块分类与介绍 一.思科厂家 1.多模光模块 型号: ...

  3. Winsock SPI-Socks5-SSL

  4. nginx优化缓冲缓存

    反向代理的一个问题是代理大量用户时会增加服务器进程的性能冲击影响.在大多数情况下,可以很大程度上能通过利用Nginx的缓冲和缓存功能减轻. 当代理到另一台服务器,两个不同的连接速度会影响客户的体验: ...

  5. sql server获取当前年月日 时分秒

    获取当前年月日(字符串): ),) 获取当前时间的时分秒(':'隔开): ),) 将年月日时分秒拼接成一条字符串: ),)),),':','')

  6. Notification使用笔记

    之前在项目中使用了Notification,现分享出来: checkNotification() function checkNotification(){ //判断是否支持Notification ...

  7. eclipse无法导入已有android项目

    问题: 今天发现我拷贝的一个android项目无法导入到eclipse,但是其它的已有android项目却可以导入 思路 现在网络这么流行,当然是上网查,得益于eclipse无法导入Android工程 ...

  8. OpenGL ES一些函数详解(一)

    glLoadIdentity和glMultMatrix   glLoadIdentity的作用是将当前模型视图矩阵转换为单位矩阵(行数和列数相同的矩阵,并且矩阵的左上角至右下角的连线上的元素都为1,其 ...

  9. POJ 2387

    最短路模板 dij 和 spfa 都可以 spfa: #include<stdio.h> #include<string.h> #include<cstring> ...

  10. 小蚂蚁搬家<贪心>

    题意: 由于预知未来可能会下雨,所以小蚂蚁决定搬家.它需要将它的所有物品都搬到新家,新家的体积为V,小蚂蚁有N件物品需要搬,每件物品的体积为Ai,但他发现:每件物品需要新家剩余体积大于等于Bi才能使它 ...