《快学 Go 语言》第 6 课 —— 字典
字典在数学上的词汇是映射,将一个集合中的所有元素关联到另一个集合中的部分或全部元素,并且只能是一一映射或者多对一映射。
数组切片让我们具备了可以操作一块连续内存的能力,它是对同质元素的统一管理。而字典则赋予了不连续不同类的内存变量的关联性,它表达的是一种因果关系,字典的 key 是因,字典的 value 是果。如果说数组和切片赋予了我们步行的能力,那么字典则让我们具备了跳跃的能力。
指针、数组切片和字典都是容器型变量,字典比数组切片在使用上要简单很多,但是内部结构却无比复杂。本节我们只专注字典的基础使用,在后续的高级章节再来分析它的内部结构。
字典的创建
关于 Go 语言有很多批评的声音,比如说它不支持范型。其实严格来说 Go 是支持范型的,只不过很弱,范型在 Go 语言里是一种很弱的存在。比如数组切片和字典类型都是支持范型的。在创建字典时,必须要给 key 和 value 指定类型。创建字典也可以使用 make 函数
package main
import "fmt"
func main() {
var m map[int]string = make(map[int]string)
fmt.Println(m, len(m))
}
----------
map[] 0
使用 make 函数创建的字典是空的,长度为零,内部没有任何元素。如果需要给字典提供初始化的元素,就需要使用另一种创建字典的方式。
package main
import "fmt"
func main() {
var m map[int]string = map[int]string{
90: "优秀",
80: "良好",
60: "及格", // 注意这里逗号不可缺少,否则会报语法错误
}
fmt.Println(m, len(m))
}
---------------
map[90:优秀 80:良好 60:及格] 3
字典变量同样支持类型推导,上面的变量定义可以简写成
var m = map[int]string{
90: "优秀",
80: "良好",
60: "及格",
}
如果你可以预知字典内部键值对的数量,那么还可以给 make 函数传递一个整数值,通知运行时提前分配好相应的内存。这样可以避免字典在长大的过程中要经历的多次扩容操作。
var m = make(map[int]string, 16)
同 Python 语言一样,字典可以使用中括号来读写内部元素,使用 delete 函数来删除元素。
package main
import "fmt"
func main() {
var fruits = map[string]int {
"apple": 2,
"banana": 5,
"orange": 8,
}
// 读取元素
var score = fruits["banana"]
fmt.Println(score)
// 增加或修改元素
fruits["pear"] = 3
fmt.Println(fruits)
// 删除元素
delete(fruits, "pear")
fmt.Println(fruits)
}
-----------------------
5
map[apple:2 banana:5 orange:8 pear:3]
map[orange:8 apple:2 banana:5]
删除操作时,如果对应的 key 不存在,delete 函数会静默处理。遗憾的是 delete 函数没有返回值,你无法直接得到 delete 操作是否真的删除了某个元素。你需要通过长度信息或者提前尝试读取 key 对应的 value 来得知。
读操作时,如果 key 不存在,也不会抛出异常。它会返回 value 类型对应的零值。如果是字符串,对应的零值是空串,如果是整数,对应的零值是 0,如果是布尔型,对应的零值是 false。
你不能通过返回的结果是否是零值来判断对应的 key 是否存在,因为 key 对应的 value 值可能恰好就是零值,比如下面的字典你就不能判断 "durin" 是否存在
var m = map[string]int {
"durin": 0 // 举个栗子而已,其实我还是喜欢吃榴莲的
}
这时候必须使用字典的特殊语法,如下
package main
import "fmt"
func main() {
var fruits = map[string]int {
"apple": 2,
"banana": 5,
"orange": 8,
}
var score, ok = fruits["durin"]
if ok {
fmt.Println(score)
} else {
fmt.Println("durin not exists")
}
fruits["durin"] = 0
score, ok = fruits["durin"]
if ok {
fmt.Println(score)
} else {
fmt.Println("durin still not exists")
}
}
-------------
durin not exists
0
字典的下标读取可以返回两个值,使用第二个返回值都表示对应的 key 是否存在。初学者看到这种奇怪的用法是需要花时间来消化的,读者不需要想太多,它只是 Go 语言提供的语法糖,内部并没有太多的玄妙。正常的函数调用可以返回多个值,但是并不具备这种“随机应变”的特殊能力 —— 「多态返回值」。字典的遍历
字典的遍历提供了下面两种方式,一种是需要携带 value,另一种是只需要 key,需要使用到 Go 语言的 range 关键字。
package main
import "fmt"
func main() {
var fruits = map[string]int {
"apple": 2,
"banana": 5,
"orange": 8,
}
for name, score := range fruits {
fmt.Println(name, score)
}
for name := range fruits {
fmt.Println(name)
}
}
------------
orange 8
apple 2
banana 5
apple
banana
orange
奇怪的是,Go 语言的字典没有提供诸于 keys() 和 values() 这样的方法,意味着如果你要获取 key 列表,就得自己循环一下,如下
package main
import "fmt"
func main() {
var fruits = map[string]int {
"apple": 2,
"banana": 5,
"orange": 8,
}
var names = make([]string, 0, len(fruits))
var scores = make([]int, 0, len(fruits))
for name, score := range fruits {
names = append(names, name)
scores = append(scores, score)
}
fmt.Println(names, scores)
}
----------
[apple banana orange] [2 5 8]
这会让代码写起来比较繁琐,不过 Go 语言官方就是没有提供,读者还是努力习惯一下吧
线程(协程)安全
Go 语言的内置字典不是线程安全的,如果需要线程安全,必须使用锁来控制。在后续锁的章节里,我们将会自己实现一个线程安全的字典。
字典变量里存的是什么?
字典变量里存的只是一个地址指针,这个指针指向字典的头部对象。所以字典变量占用的空间是一个字,也就是一个指针的大小,64 位机器是 8 字节,32 位机器是 4 字节。
可以使用 unsafe 包提供的 Sizeof 函数来计算一个变量的大小
package main
import (
"fmt"
"unsafe"
)
func main() {
var m = map[string]int{
"apple": 2,
"pear": 3,
"banana": 5,
}
fmt.Println(unsafe.Sizeof(m))
}
------
8
在遍历字典得到 keys 和 values 的例子里,我们分配了 names 和 scores 两个切片,如果把代码片断调整成下面这样,会有什么问题?
var names = make([]string, len(fruits))
var scores = make([]int, len(fruits))
下一节我们开讲 Go 语言的字符串
原文发布时间为: 2018-11-16
本文作者:码洞
本文来自云栖社区合作伙伴“码洞”,了解相关信息可以关注“码洞”。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
FullPage.js全屏插件文档及使用方法
简介 fullPage.js是一个基于jQuery的全屏滚动插件,它能够很方便、很轻松的制作出全屏网站 下载地址 下载地址 相关示例:基于fullpage.js实现的360全屏滑动效果 支持功能 支持鼠标滚动 支持前进后退和键盘控制 多个回调函数 支持手机、平板触摸事件 支持 CSS3 动画 支持窗口缩放 窗口缩放时自动调整 可设置滚动宽度、背景颜色、滚动速度、循环选项、回调、文本对齐方式等等 使用方法 1、引入文件 <link rel="stylesheet" type="text/css" href="./fullPage.js-master/jquery.fullPage.css" /> <script type="text/javascript" src="./jquery.js"></script> <script type="text/javascript" src="./fullPage.js-master/jquery.fullPage.min.js">&am...
- 下一篇
一题带你彻底理解 sleep() 和 wait()
一道Java的题目: 关于sleep()和wait(),以下描述错误的一项是: A sleep是线程类(Thread)的方法,wait是Object类的方法; B sleep不释放对象锁,wait放弃对象锁 C sleep暂停线程、但监控状态仍然保持,结束后会自动恢复 D wait后进入等待锁定池,只有针对此对象发出notify方法后获得对象锁进入运行状态 关于对象锁: 截取网上的一段话: 所有对象都自动含有单一的锁。 JVM负责跟踪对象被加锁的次数。如果一个对象被解锁,其计数变为0。在任务(线程)第一次给对象加锁的时候,计数变为1。每当这个相同的任务(线程)在此对象上获得锁时,计数会递增。 只有首先获得锁的任务(线程)才能继续获取该对象上的多个锁。 每当任务离开一个synchronized(同步)方法,计数递减,当计数为0的时候,锁被完全释放,此时别的任务就可以使用此资源。 这段话令人感到迷惑,一个对象不是只有一个锁吗?只有获得这个对象的锁才能对它进行操作,若这个对象的锁被一个线程先获得,那就其他线程就需要等待。那多次加锁什么意思,锁不是依附于对象的吗? 在往下的文章中,我暂且理解为...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS关闭SELinux安全模块
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS8编译安装MySQL8.0.19
- Windows10,CentOS7,CentOS8安装Nodejs环境
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Hadoop3单机部署,实现最简伪集群