go语言学习笔记(7)

140 阅读4分钟
字典的操作和约束

1.字典的定义

之前所接触的数组,切片container中的高级数据类型都属于针对单一元素的容器。它们用连续存储或者用互存指针的方式存储元素。
字典存储的不是单一值的集合,是键值对的集合,Go语言的字典类型是居于哈希表实现的,它的键的类型是受限制的,而值可以是任意类型。

2.哈希表的存储查询方式
在哈希表中查询某个元素,首先需要把键值作为参数传给哈希表,哈希表会通过哈希函数将键值转换为哈希值,哈希值通常是一个无符号整数。一个哈希表会持有一定数量的桶,这些桶会均匀的存储键-元素对。
然后哈希表会用这个键的哈希值的低几位去定位到一个哈希桶,然后去哈希桶中,查找这个键。找到了这个键就等于找到了元素值。
然后哈希表会将元素值的结果返回,哈希表的增删查改过程都如出一辙。

3.字典的键类型不能是哪些类型

go语言字典类型不可以是函数类型,字典类型,切片类型。在go语言中键类型的值必须支持判等操作。由于这三种类型不支持判等操作,所以字典类型的键类型不能是这些类型。
就算在字典初始化时将键的类型设置为接口类型,然后将map的键类型设置为这三种的一种,虽然在编译的时候不会出错,但是在程序运行时会抛出一个panic。程序越晚出现问题,修复的成本就越高,所以不要将字典的键类型设定为任何接口类型。
例子:
package main

import (
"fmt"
)

var test = map[interface{}]int{
"a": 1,
"[]int{2}": 2,
3: 3,
}

func main() {
fmt.Printf("%v", test[[]int{2}]) //在编译时不会出错,并且直接fmt.Printf("%v", test)也不会出错 ,但是一旦要取对应的值就会报错
}
如果键的类型是数组类型,这时候要确保数组的类型不可以是函数类型,字典类型或者切片类型。在哈希表中,每个哈希桶都会把自己包含的所有键的哈希值存储起来。Go语言会通过键值的哈希值与这些哈希值对不,如果有相等的,就再用键值本身去比对一次,来解决哈希碰撞的问题。正是因为要比对键值,所以键值类型必须能够支持判等操作。

4.应该优选选择哪些类型作为字典的键类型

字典查询的映射过程中,时间主要花费在“把键值转换为哈希值”以及“把要查找的键值与哈希桶中的值作对比”。因此这两个操作的速度越快,对应的类型就越适合作为键类型。
对于所有的基本类型、指针类型、以及数组类型、结构体类型、和接口类型,go语言都有一套哈希和判等算法。以哈希求和为例,宽度越小的类型速度通常越快,字符串由于宽度不确定,所以长度越短越快。
类型的宽度是指它的单个值需要占用的字节数。比如int8类型的宽度就为1.高级类型求哈希,实际上是依次求它的每个元素的哈希值,然后再进行合并,所以速度关键在于它的各个字段类型以及字段的数量。接口类型的哈希算法由值的实际类型决定。
最好不要用高级数据类型作为字典的键类型,它们不仅求哈希以及判等速度慢,而且它们的值还存在变数。

5.在值为nil的字典类型上执行读写操作会成功吗

由于字典是引用类型,所以声明不初始化一个字典类型的变量的时候,它的值会是nil,除了添加键 - 元素对,我们在一个值为nil的字典上做任何操作都不会引起错误。当我们试图在一个值为nil的字典中添加键 - 元素对的时候,Go 语言的运行时系统就会立即抛出一个 panic。

6.同一个时间段在不同的goruntine中对同一map进行并发操作是安全的吗

非原子操作需要加锁, map并发读写需要加锁,map操作不是并发安全的,判断一个操作是否是原子的可以使用 go run race 命令做数据的竞争检测