超级苦工
阅读 51
《快学 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 不存在会怎样?

删除操作时,如果对应的 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 语言》更多章节,用微信扫一扫上面的二维码关注公众号「码洞」

关注下面的标签,发现更多相似文章
评论
相关推荐
NANEA莱妮雅小肌蛋面膜,睡出来的天生丽质

护肤品行业永远不缺变化,有些变化是真的能带来好处的,有些变化是为了创新而创新,而NANEA莱妮雅意大利腊菊小肌蛋清肤面膜的变化,则是在创新的基础上为广大女性带来精致护肤实实在在好处的。 这一刻,抓住夜...

百年品牌NANEA莱妮雅——在服务中传递态度,在产品中演绎匠心

品牌历史沉淀,自然经典再现。百年来,NANEA莱妮雅一直为时尚年轻、简约轻奢品牌定位而努力进步,将天然植萃护肤理念传扬给更多人,这也已经成为品牌在护肤领域中不断耕耘的不竭动力和宗旨。 百年品牌NANE...

《快学 Go 语言》第 13 课 —— 并发与安全

来源: 知乎 原文: 《快学 Go 语言》第 13 课 —— 并发与安全 上一节我们提到并发编程不同的协程共享数据的方式除了通道之外还有就是共享变量。虽然 Go 语言官方推荐使用通道的方式来共享数据,...

《快学 Go 语言》第 12 课 —— 通道

来源: 知乎 原文: 《快学 Go 语言》第 12 课 —— 通道 不同的并行协程之间交流的方式有两种,一种是通过共享变量,另一种是通过队列。Go 语言鼓励使用队列的形式来交流,它单独为协程之间的队列...

《快学 Go 语言》第 11 课 —— 千军万马跑协程

来源: 知乎 原文: 《快学 Go 语言》第 11 课 —— 千军万马跑协程 协程和通道是 Go 语言作为并发编程语言最为重要的特色之一,初学者可以完全将协程理解为线程,但是用起来比线程更加简单,占用...

测试发布

测试文章发布...

《快学 Go 语言》第 10 课 —— 错误与异常

来源: 知乎 原文: 《快学 Go 语言》第 10 课 —— 错误与异常 Go 语言的异常处理语法绝对是独树一帜,在我见过的诸多高级语言中,Go 语言的错误处理形式就是一朵奇葩。一方面它鼓励你使用 C...

《快学 Go 语言》第 9 课 —— 接口

来源: 知乎 原文: 《快学 Go 语言》第 9 课 —— 接口 接口是一个对象的对外能力的展现,我们使用一个对象时,往往不需要知道一个对象的内部复杂实现,通过它暴露出来的接口,就知道了这个对象具备哪...

《快学 Go 语言》第 8 课 —— 结构体

来源: 知乎 原文: 《快学 Go 语言》第 8 课 —— 结构体 本节我们要开讲 Go 语言在数据结构上最重要的概念 —— 结构体。如果说 Go 语言的基础类型是原子,那么结构体就是分子。分子是原子...

《快学 Go 语言》第 7 课 —— 字符串

来源: 知乎 原文: 《快学 Go 语言》第 7 课 —— 字符串 字符串通常有两种设计,一种是「字符」串,一种是「字节」串。「字符」串中的每个字都是定长的,而「字节」串中每个字是不定长的。Go 语言...

《快学 Go 语言》第 6 课 —— 字典

来源: 知乎 原文: 《快学 Go 语言》第 6 课 —— 字典 字典在数学上的词汇是映射,将一个集合中的所有元素关联到另一个集合中的部分或全部元素,并且只能是一一映射或者多对一映射。<img ...

《快学 Go 语言》第 5 课 —— 灵活的切片

来源: 知乎 原文: 《快学 Go 语言》第 5 课 —— 灵活的切片 切片无疑是 Go 语言中最重要的数据结构,也是最有趣的数据结构,它的英文词汇叫 slice。所有的 Go 语言开发者都津津乐道地...

《快学 Go 语言》第 4 课 —— 低调的数组

来源: 知乎 原文: 《快学 Go 语言》第 4 课 —— 低调的数组 Go 语言里面的数组其实很不常用,这是因为数组是定长的静态的,一旦定义好长度就无法更改,而且不同长度的数组属于不同的类型,之间不...

《快学 Go 语言》第 3 课 —— 分支与循环

来源: 知乎 原文: 《快学 Go 语言》第 3 课 —— 分支与循环 程序 = 数据结构 + 算法上面这个等式每一个初学编程的同学都从老师那里听说过。它并不是什么严格的数据公式,它只是对一般程序的简...

《快学 Go 语言》第 2 课 —— 变量基础

来源: 知乎 原文: 《快学 Go 语言》第 2 课 —— 变量基础 任何一门语言里面最基础的莫过于变量了。如果把内存比喻成一格一格整齐排列的储物箱,那么变量就是每个储物箱的标识,我们通过变量来访问计...

《快学 Go 语言》第 1 课 —— Hello World

来源: 知乎 原文: 《快学 Go 语言》第 1 课 —— Hello World Go 语言的 Logo最初 Go 语言的 Logo 是一只可爱的地鼠,地鼠昼伏夜出的习性让它显得很有 Geek 范。...

从零开始学调优-Java 全技术栈 性能调优超清原画无密点击自行下载

从零开始学调优-Java 全技术栈 性能调优超清原画无密【点击自行下载】为什么说:性能调优技术一直是市场上的香饽饽,是面试考察关键的一环,是工作中技术能力的分水岭?用最接地气话回答就是:掌握性能优化,...

体系课-数据可视化入门到精通-打造前端差异化竞争力完整无密

体系课-数据可视化入门到精通-打造前端差异化竞争力完整无密【点击自行下载】首门微体系课,不谦虚的讲,全网打着灯笼也找不到这么精炼、系统、实用的数据可视化课程,通过学习课程,不管是数据报表、数据大屏、移...

Java架构师成长直通车完整无密云盘分享

Java架构师成长直通车完整无密云盘分享【点击下载】 国内外一线大厂技术大咖与某课网组成专家团队12个月磨一剑千万级电商项目从0到1到100全过程涵盖Java程序员不同成长阶段的问题及优选解决方案 #...

测试图片

...