Go语言map
本文内容纲要:
-内部实现
-声明map
-初始化map
-使用Map
-map容量
-map的排序
-在函数间传递Map
map是一种特殊的数据结构:一种元素对(pair)的无序集合,pair的一个元素是key,对应的另一个元素是value,所以这个结构也称为关联数组或字典。这是一种快速寻找值的理想结构:给定key,对应的value可以迅速定位。
内部实现
Map是给予散列表来实现,就是我们常说的Hash表,所以我们每次迭代Map的时候,打印的Key和Value是无序的,每次迭代的都不一样,即使我们按照一定的顺序存在也不行。
Map的散列表包含一组桶,每次存储和查找键值对的时候,都要先选择一个桶。如何选择桶呢?就是把指定的键传给散列函数,就可以索引到相应的桶了,进而找到对应的键值。
这种方式的好处在于,存储的数据越多,索引分布越均匀,所以我们访问键值对的速度也就越快,当然存储的细节还有很多,大家可以参考Hash相关的知识,这里我们只要记住Map存储的是无序的键值对集合。
声明map
map是引用类型,可以使用如下声明:
varvariableNamemap[keyType]valueType
[keyType]
和valueType
之间允许有空格,但是gofmt移除了空格
在声明的时候不需要知道map的长度,map是可以动态增长的。
未初始化的map的值是nil。
key可以是任意可以用==或者!=操作符比较的类型,比如string、int、float。所以数组、切片和结构体不能作为key,但是指针和接口类型可以。如果要用结构体作为key可以提供Key()和Hash()方法,这样可以通过结构体的域计算出唯一的数字或者字符串的key。
value可以是任意类型的;通过使用空接口类型,我们可以存储任意值,但是使用这种类型作为值时需要先做一次类型断言。
map传递给函数的代价很小:在32位机器上占4个字节,64位机器上占8个字节,无论实际上存储了多少数据。通过key在map中寻找值是很快的,比线性查找快得多,但是仍然比从数组和切片的索引中直接读取要慢100倍;所以如果你很在乎性能的话还是建议用切片来解决问题。
示例:
varmap1map[string]int
初始化map
Map的创建有make
函数,Map字面量。make
函数我们用它创建过切片,除此之外,它还可以用来创建Map。
map是引用类型的:内存用make方法来分配。
dict:=make(map[string]int)
示例中创建了一个键类型为string
的,值类型为int
的map。现在创建好之后,这个map是空的,里面什么都没有,我们给存储一个键值对。
dict:=make(map[string]int)
dict["itbsl"]=25
存储了一个Key为itbsl的,Value为25的键值对数据。
此外还有一种使用map字面量的方式创建和初始化map,对于上面的例子,我们可以同等实现。
dict:=map[string]int{"itbsl":25}
使用一个大括号进行初始化,键值对通过:
分开,如果要同时初始化多个键值对,使用逗号分割。
dict:=map[string]int{"itbsl":25,"kevin":50}
当然我们可以不指定任何键值对,也就是一个空map。
dict:=map[string]int{}
不管怎么样,使用map的字面量创建一定要带上大括号。如果我们要创建一个nil
的Map怎么做呢?nil
的Map是未初始化的,所以我们可以只声明一个变量,既不能使用map字面量,也不能使用make
函数分配内存。
vardictmap[string]int
这样就好了,但是这样我们是不能操作存储键值对的,必须要初始化后才可以,比如使用make
函数,为其开启一块可以存储数据的内存,也就是初始化。
vardictmap[string]int
dict=make(map[string]int)
dict["itbsl"]=25
fmt.Println(dict)
使用Map
Map的使用很简单,和数组切片差不多,数组切片是使用索引,Map是通过键。
dict:=make(map[string]int)
dict["itbsl"]=25
以上示例,如果键itbsl
存在,则对其值修改,如果不存在,则新增这个键值对。
获取一个Map键的值也很简单,和存储差不多,还是给予上面的例子。
age:=dict["itbsl"]
在GoMap中,如果我们获取一个不存在的键的值,也是可以的,返回的是值类型的零值,这样就会导致我们不知道是真的存在一个为零值的键值对呢,还是说这个键值对就不存在。对此,Map为我们提供了检测一个键值对是否存在的方法。
age,exists:=dict["itbsl"]
看这个例子,和获取键的值没有太大区别,只是多了一个返回值。第一个返回值是键的值;第二个返回值标记这个键是否存在,这是一个boolean
类型的变量,我们判断它就知道该键是否存在了。这也是Go多值返回的好处。
如果我们想删除一个Map中的键值对,可以使用Go内置的delete
函数。
delete(dict,"itbsl")
delete
函数接受两个参数,第一个是要操作的Map,第二个是要删除的Map的键。
delete函数删除不存在的键也是可以的,只是没有任何作用。
想要遍历Map的话,可以使用forrange
风格的循环,和遍历切片一样。
dict:=map[string]int{"itbsl":25}
forkey,value:=rangedict{
fmt.Println(key,value)
}
这里的range
返回两个值,第一个是Map的键,第二个是Map的键对应的值。这里再次强调,这种遍历是无序的,也就是键值对不会按既定的数据出现,如果想安顺序遍历,可以先对Map中的键排序,然后遍历排序好的键,把对应的值取出来,下面看个例子就明白了。
funcmain(){
dict:=map[string]int{"kevin":40,"itbsl":25}
varnames[]string
forname:=rangedict{
names=append(names,name)
}
sort.Strings(names)//排序
for_,key:=rangenames{
fmt.Println(key,dict[key])
}
}
这个例子里有个技巧,range
一个Map的时候,也可以使用一个返回值,这个默认的返回值就是Map的键。
map容量
和数组不同,map可以根据新增的key-value对动态的伸缩,因此它不存在固定长度或者最大限制。但是你也可以选择标明map的初始容量capacity
,就像这样:make(map[keytype]valuetype,cap)
。例如:
map2:=make(map[string]float,100)
当map增长到容量上限的时候,如果再增加新的key-value对,map的大小会自动加1。所以出于性能的考虑,对于大的map或者会快速扩张的map,即使只是大概知道容量,也最好先标明。
map的排序
map默认是无序的,不管是按照key还是按照value默认都不排序。
如果你想为map排序,需要将key(或者value)拷贝到一个切片,再对切片排序,然后可以使用切片的for-range方法打印出所有的key和value。
在函数间传递Map
函数间传递Map是不会拷贝一个该Map的副本的,也就是说如果一个Map传递给一个函数,该函数对这个Map做了修改,那么这个Map的所有引用,都会感知到这个修改。
funcmain(){
dict:=map[string]int{"kevin":40,"itbsl":25}
modify(dict)
fmt.Println(dict["kevin"])
fmt.Printf("main函数dict的地址为:%p\n",dict)
}
funcmodify(dictmap[string]int){
dict["kevin"]=10
fmt.Printf("modify函数dict的地址为:%p\n",dict)
}
输出结果为:
modify函数dict的地址为:0xc000076120
10
main函数dict的地址为:0xc000076120
上面这个例子输出的结果是10
,也就是说已经被函数给修改了,可以证明传递的并不是一个Map的副本。这个特性和切片是类似的,这样就会更高,因为复制整个Map的代价太大了。
本文内容总结:内部实现,声明map,初始化map,使用Map,map容量,map的排序,在函数间传递Map,
原文链接:https://www.cnblogs.com/itbsl/p/10043550.html