一个“键”和一个“值”分别代表了一个从属于某一类型的独立值,把它们两个捆绑在一起就是一个键值对了。
在 Go 语言规范中,应该是为了避免歧义,他们将键值对换了一种称呼,叫做:“键 - 元素对”。
字典的健类型不能是哪些类型?
典型回答是:Go 语言字典的键类型不可以是函数类型、字典类型和切片类型。
为什么这些类型不可以?
Go 语言规范规定,在键类型的值之间必须可以施加操作符==和!=,也就是说健类型必须要支持判等操作。
为什么键类型的值必须支持判等操作?
- Go 语言的字典类型其实是一个哈希表(hash table)的特定实现。
- 哈希表会先用哈希函数(hash function)把键值转换哈希值。
- 哈希值通常是一个无符号的整数。一个哈希表会持有一定数量的桶(bucket),也可称之为哈希桶,这些哈希桶会均匀地储存其所属哈希表收纳的那些键- 元素对。
- 键的查找就是键的哈希值与哈希桶里的值做判等操作
- 如果有相等的,那就再用键值本身去对比一次。
为什么还要对比一次?
原因是,不同值的哈希值是可能相同的。这有个术语,叫做“哈希碰撞”。
应该优先考虑哪些类型作为字典的键类型?
优先选用数值类型和指针类型,通常情况下类型的宽度越小越好。
如果非要选择字符串类型的话,最好对键值的长度进行额外的约束
求哈希和判等操作的速度越快,对应的类型就越适合作为键类型。
以求哈希的操作为例,宽度越小的类型速度通常越快。
为什么要对字符串类型的键值长度进行额外的约束?
对于字符串类型,由于它的宽度是不定的,所以要看它的值的具体长度,长度越短求哈希越快。
类型的宽度是什么?
类型的宽度是指它的单个值需要占用的字节数。比如,bool、int8和uint8类型的一个值需要占用的字节数都是1,因此这些类型的宽度就都是1。
那高级类型的宽度如何求得?
- 对数组类型的值求哈希实际上是依次求得它的每个元素的哈希值并进行合并,所以速度就取决于它的元素类型以及它的长度。细则同上。
- 与之类似,对结构体类型的值求哈希实际上就是对它的所有字段值求哈希并进行合并,所以关键在于它的各个字段的类型以及字段的数量。
- 而对于接口类型,具体的哈希算法,则由值的实际类型决定。
但不建议使用这些高级数据类型作为字典
为什么不建议高级数据类型作为字典的键类型?
不仅仅是因为对它们的值求哈希,以及判等的速度较慢,更是因为在它们的值中存在变数。
比如,对一个数组来说,可以任意改变其中的元素值,但在变化前后,它却代表了两个不同的键值。
在值为nil的字典上执行读操作会成功吗,那写操作呢?
除了添加键 - 元素对,我们在一个值为nil的字典上做任何操作都不会引起错误。
当我们试图在一个值为nil的字典中添加键- 元素对的时候,Go 语言的运行时系统就会立即抛出一个panic。
这涉及程序运行时的稳定性。
字典类型的值是并发安全的吗?
非原子操作的都不是并发安全的,map并不保证并发安全。
用1.9之后新加入的sync.Map可以做到并发安全,如果不用的话需要对goroutine加读写锁。
判断一个操作是否是原子的可以使用 go run race 命令做数据的竞争检测。