目录
- map中的key的数据类型
- key的几种数据类型举例
- 判断某个数据类型是否可以作为key的数据类型
- map基本操作
- map创建
- map增删改查
- 查找
- 增加,修改
- 删除
- map遍历
- map遍历易错点举例 - 保存临时变量的地址
- map其他操作
- 获取map中元素的个数
- go中的map是hash表的一个引用,类型写为:map[key]value,其中的key, value分别对应一种数据类型,如map[string]string
- 要求所有的key的数据类型相同,所有value数据类型相同(注:key与value可以有不同的数据类型)
map中的key的数据类型
- map中的每个key在keys的集合中是唯一的,而且需要支持 == or != 操作
- key的常用类型:int, rune, string, 结构体(每个元素需要支持 == or != 操作), 指针, 基于这些类型自定义的类型
float32/64 类型从语法上可以作为key类型,但是实际一般不作为key,因为其类型有误差
key的几种数据类型举例
下面使用具体例子介绍几种可以作为key的数据类型以及不能作为key的数据类型
- string类型可以作为key的数据类型,因为其支持 == 比较操作,如变量 m0
- interface{}类型可以作为key,但是需要加入的key的类型是可以比较的据类型, 如果加入key的元素的变量类型是不能比较的,会发生panix,如变量 m1
- 数组支持比较,可以为key的数据类型,如变量 m2
- slice 不可以, 因为其不支持 == != 操作,不可以作为map key的数据类型(准确说slice类型只能与nil比较,其他的都不可以),如变量 m3
- 如果struct中的所有元素都支持比较操作,那么这个struct类型可以作为key的数据类型,如变量 m4
- 如果struct中的某个元素不支持比较操作,那么这个struct类型不可以作为key的数据类型,如变量m5
// m0 string
{
var m0 map[string]string // 定义map类型变量m0,key的类型为string,value的类型string
fmt.Println(m0) // 输出: map[]
}
// m1 interface{}
{
var m1 map[interface{}]string
m1 = make(map[interface{}]string)
//m1[[]byte("k2")]="v2" // panic: runtime error: hash of unhashable type []uint8
m1[123] = "123"
m1[12.3] = "123"
fmt.Println(m1) // map[123:123 12.3:123]
}
// m2 数组
{
a2 := [3]int{1, 2, 3}
var m2 map[[3]int]string
m2 = make(map[[3]int]string)
m2[a2] = "m2"
fmt.Println(m2) // map[[1 2 3]:m2]
}
// m3 slice
{
//var m3 map[[]byte]string // 报错: invalid map key type []byte
//fmt.Println(m3)
}
// m4 可以,book1里面的元素都是支持== !=
{
type book1 struct {
name string
}
var m4 map[book1]string
fmt.Println(m4) // 输出: map[]
}
// m5 不可以, text元素类型为[]byte, 不满足key的要求
{
// type book2 struct {
// name string
// text []byte //没有这个就可以
// }
//var m5 map[book2]string //invalid map key type book2
//fmt.Println(m5)
}
如何判断某个数据类型是否可以作为map key的数据类型
- 直接建立一个map,然后运行,如果报错说明不支持;但是interface{}特殊,只有在加入变量的时候才可以判断是否支持,因为在定义的时候,并不知道实际放入key的数据的具体类型
- 定义两个某种类型的变量,使用 == 比较一下, 如果不报错说明支持,如下面的例子 两个string类型的变量进行比较操作,返回true说明支持; 两个slice变量进行比较操作报错,说明不支持
var s1 = "ss"
var s2 = "ss"
fmt.Println(s1 == s2) // 输出: true
// var b1, b2 []byte
// fmt.Println(b1 == b2) // 报错: invalid operation: b1 == b2 (slice can only be compared to nil)
map基本操作
map创建
- 两种创建的方式:一是通过字面值;二是通过make函数
// 1 字面值
{
m1 := map[string]string{
"m1": "v1", // 定义时指定的初始key/value, 后面可以继续添加
}
_ = m1
}
// 2 使用make函数
{
m2 := make(map[string]string) // 创建时,里面不含元素,元素都需要后续添加
m2["m2"] = "v2" // 添加元素
_ = m2
}
// 使用两种方法定义一个空的map
{
m3 := map[string]string{}
m4 := make(map[string]string)
_ = m3
_ = m4
}
map增删改查
查找
// 创建
m := map[string]string{
"a": "va",
"b": "vb",
}
// 查
{
// v := m[k] // 从m中取键k对应的值给v,如果k在m中不存在,则将value类型的零值赋值给v
// v, ok := m[k] // 从m中取键k对应的值给v,如果k存在,ok=true,如果k不存在,将value类型的零值赋值给v同时ok=false
// 查1 - 元素不存在
v1 := m["x"] //
v2, ok2 := m["x"]
fmt.Printf("%#v %#v %#v\n", v1, v2, ok2) // "" "" false
// 查2 - 元素存在
v3 := m["a"]
v4, ok4 := m["a"]
fmt.Printf("%#v %#v %#v\n", v3, v4, ok4) //"va" "va" true
}
增加,修改
- 当key不存在map中的时候为增加操作,当key存在于map的时候修改操作,会使用新的value覆盖掉旧的value
// 创建
m := map[string]string{
"a": "va",
"b": "vb",
}
// 增加,修改
{
// k不存在为增加,k存在为修改
m["c"] = "" // 增加操作
m["c"] = "11" // 重复增加(key相同),使用新的值覆盖
fmt.Printf("%#v %#v\n", m, len(m)) // map[string]string{"a":"va", "b":"vb", "c":"11"} 3
}
- 两种方式可以判断map中是否存在某个key
- 方法一 使用key获取value,然后与value数据类型的零值进行比较,如果返回了类型零值说明不存在这个key。如下面的方式一,这种方式比较简单,但是会存在一个小的瑕疵,比如key真的存在于map中,但是value的值是类型对应的零值的时候,会出现判断失误,比如 m["c"]="",string类型的零值为 "", 这种情况 key="c"是存在的,但是使用这种方式会判断为"c"不存在于map中
- 方法二 返回值多加一个变量来判断是否存在。返回的第一个值是map中的key对应的value值,第二个是一个bool型变量,如果返回true说明变量存在于map中,如果false说明变量不存在map中
// 创建
m := map[string]string{
"a": "va",
"b": "vb",
}
// 一 判断key是否存在, 简版,有瑕疵
{
v := m["a"]
fmt.Println(v)
}
// 二 判断key是否存在,准确
{
v, exist := m["a"]
fmt.Println(v)
fmt.Println(exist)
}
删除
// 创建
m := map[string]string{
"a": "va",
"b": "vb",
}
// 删, 使用内置函数删除k/v对
{
// delete(m, k) 将k以及k对应的v从m中删掉;如果k不在m中,不执行任何操作
delete(m, "x") // 删除不存在的key,原m不影响
delete(m, "a") // 删除存在的key
fmt.Printf("%#v %#v\n", m, len(m)) // map[string]string{"b":"vb", "c":"11"} 2
delete(m, "a") // 重复删除不报错,m无影响
fmt.Printf("%#v %#v\n", m, len(m)) /// map[string]string{"b":"vb", "c":"11"} 2
}
map遍历
- 遍历的顺序是随机的
- 使用for range遍历的时候,k,v使用的同一块内存,这也是容易出现错误的地方
// 创建
m := map[string]string{
"a": "va",
"b": "vb",
}
// 遍历
{
for k, v := range m {
fmt.Printf("k:[%v].v:[%v]\n", k, v) // 输出k,v值
}
}
// k:[a].v:[va]
// k:[b].v:[vb]
map遍历易错点举例 - 保存临时变量的地址
由于遍历的时候,遍历k, v使用的同一块地址,同时这块地址是临时分配的。虽然地址没有变化,但内容在一直变化。在下面的例子中,当遍历完成后,v的内容是map遍历时最后遍历的元素的值(map遍历无序,每次不确定哪个元素是最后一个元素)。当程序将v的地址放入到slice中的时候,slice在不断地v的地址插入,由于v一直是那块地址,因此slice中的每个元素记录的都是v的地址。因此当打印slice中的内容的时候,都是同一个值
// 创建
m := map[string]string{
"a": "va",
"b": "vb",
}
// 保存临时变量的地址
{
var bs []*string
for k, v := range m {
fmt.Printf("k:[%p].v:[%p]\n", &k, &v) // 这里的输出可以看到,k一直使用同一块内存,v也是这个状况
bs = append(bs, &v) // 对v取了地址
}
// 输出
// k:[0xc00004a1c0].v:[0xc00004a1d0]
// k:[0xc00004a1c0].v:[0xc00004a1d0]
for _, b := range bs {
fmt.Println(*b) // 输出都是1或者都是2
}
// 输出
// vb
// vb
}
map其他操作
- 获取map中元素的个数
// 创建
m := map[string]string{
"a": "va",
"b": "vb",
}
fmt.Println(len(m)) // len(m) 获得m中key/value对的个数