字典是一种数据结构,用于存储一系列无序的键值对。
字典是基于键来存储值。字典功能强大的地方是能够基于键快速检索数据。
键就像索引一样,指向与键关联的值。
1.内部实现
字典是一个集合,可以使用类似处理数组和切片的方式迭代字典中的元素。
但字典是无序的集合,意味着没有办法预测键值对被返回的顺序。
即使使用同样的顺序保存键值对,每次迭代字典的时候顺序也可能不一样。
无序的原因是字典使用了散列表。
字典的散列表包含一组桶。在存储、删除或者查找键值对的时候,所有操作都要先选择一个桶。
把操作字典时指定的键传给字典的散列函数,就能选中对应的桶。
这个散列函数的目的是生成一个索引,这个索引最终将键值对分布到所有可用的桶里。
随着字典存储的增加,索引分布越均匀,访问键值对的速度就越快。
如果你在字典里存储了10000个元素,你不希望每次查找都要访问10000个键值对才能找到需要的元素,你希望查找键值对的次数越少越好。
对于有10000个元素的字典,每次查找都只需要查找8个键值对才是一个分布得比较好得字典。
字典通过合理数量的桶来平衡键值对得分布。
键会转化为一个数值(散列值)。这个数值落在字典已有桶的序号范围内表示一个可用于存储的桶的序号。
之后这个数值就被用于选择桶,用于存储或者查找指定的键值对。
对Go语言的字典来说,生成的散列键的一部分,具体来说是低位(LOB)被用来选择桶。
字典使用两个数据结构来存储数据。第一个数据结构是一个数组,内部存储的是用于选择桶的散列键的高八位值,
这个数值就被用于选择桶,用于存储或者查找指定的键值对。
第二个数据结构是一个字节数组,用于存储键值对。该字节数组先依次存储了这个桶里所有的键,之后依次存储了这个桶里所有的值。
实现这种键值对的存储方式目的在于减少每个桶所需的内存。
2.创建和初始化
(1)使用make声明字典
Go语言中有很多中方法可以创建并初始化字典,可以使用内置的make函数,也可以使用字典字面量。
//创建一个字典,键的类型是string,值得类型是int dict := make(map[string]int) //创建一个字典,键和值得类型都是string //使用两个键值对初始化字典 dict := map[string]string{"name":"kebi", "sex":"boy"}
(2)使用字典字面量声明空字典
创建字典时更常用得方法是使用字典字面量。字典的初始长度会根据初始化时指定得键值对的数量来确定。
字典的键可以是任何值。这个值的类型可以是内置的类型,也可以是结构类型,只要这个值可以使用==运算符做比较。
切片、函数以及包含切片的结构类型这些类型由于具有引用语义,不能作为字典的键,使用这些键会造成编译错误。
dict := map[[]string]string{} //invalid map key type []string
(3)声明一个存储字符串切片的字典
没有任何理由阻止用户使用切片作为字典的值。
//创建一个字典,使用字符串切片作为值。 dict := map[int][]string{}
3.使用字典
(1)为字典赋值
键值对赋值给字典,是通过指定适当类型的键并给这个件赋予一个值来完成的。
//创建一个空字典,用来存储颜色以及颜色对应的十六进制代码 colors := map[string]string{} //将Red的代码加入到字典 colors["Red"] = "#da1337"
(2)对nil字典赋值时的语言运行时错误
可通过声明一个未初始化的字典来创建一个值为nil的字典。
nil字典不能用于存储键值对,否则,会产生一个语言运行错误
//通过声明字典创建一个nil字典 var colors map[string]string //将red的代码加入到字典 colors["red"] = "#da1337" //assignment to entry in nil map
(3)从字典获取值并判断键是否存在
测试字典里是否存在某个键是字典的一个重要操作。
这样操作允许用户写一些逻辑来确定是否完成某个操作或者是否在字典里缓存了一个特定数据。
这样操作也可以用来比较两个字典,来确定那个键值对互相匹配,那些键值对不匹配。
从字典取值时有两个选择。第一个选择是,可以同时获得值,以及一个表示这个键是否存在的标志。
//获取键blue对应的值 value, exists := colors["blue"] //这个键存在吗? if exists { fmt.PrintLn(value) }
(4)从字典获取值,并通过该值判断键是否存在
另一个选择是,只返回键对应的值,然后判断这个值是不是零值来确定键是否存在。
//获取键Blue对应得值 value := colors["blue"] //这个键存在吗? if value != " " { fmt.PrintLn(value) }
在Go语言里,通过键来索引字典时,即便这个键不存在也总会返回一个值。
在这种情况下,返回得是该值对应得类型的零值。
(5)使用range迭代字典
迭代字典里的所有值和迭代数组或切片一样,使用关键字range。
对字典来说,range返回的不是索引的值,而是键值对。
package main import "fmt" func main() { //创建一个字典,存储颜色以及颜色对应的十六进制代码 colors := map[string]string{ "red": "#34567", "blue": "#6789", "pink": "#drtuft", "yellow": "#df67", } //显式所有颜色 for key, value := range colors { fmt.Printf("Key: %s, value: %s ", key, value) } } /* 结果 Key: red, value: #34567 Key: blue, value: #6789 Key: pink, value: #drtuft Key: yellow, value: #df67 */
(6)从字典中删除一项
如果想要把一个键值从字典里删除,就使用内置的delete函数。
//删除键为red的键值对 delete(colors, "red)
4.在函数间传递字典
在函数键传递字典并不会制造出该字典的一个副本。
实际上,当传递一个字典给一个函数,并对这个函数做出修改时,所有对这个字典的引用都会察觉到这个修改。
package main import "fmt" func recoveColor(colors map[string]string, key string) { delete(colors, key) } func main() { //创建一个字典,存储颜色以及颜色对应的十六进制代码 colors := map[string]string{ "red": "#34567", "blue": "#6789", "pink": "#drtuft", "yellow": "#df67", } //显式所有颜色 for key, value := range colors { fmt.Printf("Key: %s, value: %s ", key, value) } //调用函数移除指定的键 recoveColor(colors, "red") //显式所有颜色 fmt.Println(" ") for key, value := range colors { fmt.Printf("Key: %s, value: %s ", key, value) } } /* 直接修改原字典 Key: red, value: #34567 Key: blue, value: #6789 Key: pink, value: #drtuft Key: yellow, value: #df67 Key: yellow, value: #df67 Key: blue, value: #6789 Key: pink, value: #drtuft */