Go语言容器之map、list和nil
一、map
- map和C++中map一样,里面存放的是key-value键值对
- 在Go中map是引用类型,声明语法:var map变量名 map[key的类型]value的类型
结果:package mainimport "fmt"func main() {var mp map[string]intmpls := map[string]int{"one":1, "two":2, "three":3}mpmk := make(map[string]int)mp = mplsmpmk["语文"] = 1mpmk["数学"] = 2mpmk["英语"] = 4mp["four"] = 4mp["five"] = 5fmt.Println("mp[one]", mp["one"])fmt.Println("mp[ten]", mp["ten"])fmt.Println("mp", mp)fmt.Println("mpmk[语文]", mpmk["语文"])fmt.Println(mpmk) }
1.map的容量
- 和数组不一样,map可以根据新增的key-value进行动态的增加删除,所以不存在长度、最大限制;但是可以选择标明map的初始容量capacity,格式如下:
make(map[key类型]value类型, cap)
2.map的遍历
- 遍历map可以用for循环语句和range关键字进行遍历
结果:package mainimport "fmt"func main() {mpls := map[string]int{"one":1, "two":2, "three":3, "four":4, "five":5, "six":6, "seven":7, "eight":8, "nine":9, "ten":10}//遍历mapfor key, value := range mpls{fmt.Printf("key:%s",key)fmt.Printf(": value:%d\n", value)fmt.Println("value:", mpls[key])}fmt.Println("只遍历key:")//只遍历keyfor key, _ := range mpls{fmt.Printf("key:%s ", key)}fmt.Println("\n只遍历value:")//只遍历valuefor _, value := range mpls{fmt.Printf("value:%d ", value)} }
3.map中元素的删除
- delete(map变量名, key对应的键)
结果:package mainimport "fmt"func main() {mpls := map[string]int{"one":1, "two":2, "three":3, "four":4, "five":5, "six":6, "seven":7, "eight":8, "nine":9, "ten":10}//遍历mapfor key, value := range mpls{fmt.Printf(" key: %s, value: %d||",key, value)}delete(mpls, "five")delete(mpls,"nine")delete(mpls,"three")fmt.Println("\n删除后:")fmt.Println(mpls) }
4.并发map(sync.Map)
- Go语言中,map在并发情况下,只读是线程安全的,但是同时读写线程是不安全的
- 需要并发读写的时候,一般是用加锁的方式。go中有一个效率高的并发安全的map,即snyc.Map。
- sync.Map有以下特征:
(1)无须初始化,直接声明即可
(2)sync.Map和map不一样,不可以用map的方式对其进行操作。而是有自己特有的方式,Store表示存储,Load表示获取,Delete表示删除
(3)使用Range配合一个回调函数进行遍历错做,通过回调函数返回内部遍历出来的值,Range参数中回调函数的返回值在需要继续迭代遍历时,返回true,终止遍历时返回false - 举例:
结果:package mainimport ("fmt""sync" )func main() {var snmp sync.Map//存储数据snmp.Store("语文", 100)snmp.Store("数学", 130)snmp.Store("英语", 120)snmp.Store("生物", 90)snmp.Store("物理", 30)//查看数据fmt.Println(snmp.Load("生物"))fmt.Println(snmp.Load("数学"))//删除数据snmp.Delete("生物")//遍历snmp.Range(func(k, v interface{})bool{fmt.Println("iterate:", k, v)return true}) }
二、list
- 在Go中,列表list内部的实现原理是双链表,能够高效地进行任意位置元素的插入和删除操作
1.列表的初始化
- 方式一:通过container/list包中的New()函数来进行初始化。变量名 := list.New()
- 方式二:通过var关键字声明初始化list。var 变量名 list.List
2.列表的插入、删除
- 向列表中插入元素,因为是双链表,可以从队尾、队首两头直接插。PushFront向队首前方插入元素,PushBack向队尾后方插入元素,InsertAfter在队列中某元素之后插入一个元素,InsertBefore在队列某元素之前插入一个元素
- Remove移除某个元素
package mainimport ("container/list""fmt"
)func main() {ls := list.New()ls.PushFront("d")//队首添加dele2 := ls.PushBack("e")//队尾添加e。且存放e元素的句柄。队列:d->eele1 := ls.PushFront("c")//队首添加c。且存放c元素的句柄。队列:c->d->els.PushBack("g")//队尾添加f。队列:c->d->e->gls.PushFront("a")//队首添加a。队列:a->c->d->e->g//遍历队列:for i := ls.Front(); i != nil; i = i.Next(){fmt.Printf(" %v -> ", i.Value)}fmt.Println()ls.InsertBefore("b", ele1)//在c元素前面添加b。队列:a->b->c->d->e->gls.InsertAfter("f", ele2)//在e元素后面添加f。队列:a->b->c->d->e->f->gele3 := ls.InsertAfter("i", ele2)//在e元素后面添加i。队列:a->b->c->d->e->i->f->g//遍历队列:for i := ls.Front(); i != nil; i = i.Next(){fmt.Printf(" %v -> ", i.Value)}ls.Remove(ele3)//删除元素ifmt.Println()//遍历队列:for i := ls.Front(); i != nil; i = i.Next(){fmt.Printf(" %v -> ", i.Value)}
}
结果:
三、零值、空值nil
- 在Go语言中,布尔类型的零值(初始化值)为false,数值类型的零值为0,字符串类型的零值为空字符串"",而指针、切片、映射、通道和接口类型的零值都是nil空值
- Go中,nil是一个预定义好的标识符。
1.nil的特性
(1)nil是不能比较的,无论nil的类型是否相同也都不能进行比较,会编译报错
package mainimport "fmt"func main() {fmt.Println(nil == nil)
}
结果:(2)nil不是关键字或者保留字
(3)nil没有默认的类型
(4)不同类型的nil的指针是一样的
package mainimport ("fmt"
)func main() {var arr[]intvar mp map[int]intvar num intfmt.Printf("%p\n", arr)fmt.Printf("%p\n", mp)fmt.Printf("%p\n", num)
}
结果:
(5)nil是指针、切片、map、channel、接口、函数的零值
package mainimport ("fmt"
)func main() {var ptr *stringvar slce []intvar arr[]intvar mp map[int]intvar cha chan intvar fn func()var ins interface{}fmt.Printf("%#v\n", ptr)fmt.Printf("%#v\n", slce)fmt.Printf("%#v\n", arr)fmt.Printf("%#v\n", mp)fmt.Printf("%#v\n", cha)fmt.Printf("%#v\n", fn)fmt.Printf("%#v\n", ins)
}
结果:
(6)不同类型的nil值占用的内存大小不一样,按照类型大小
package mainimport ("fmt""unsafe"
)func main() {var ptr *stringvar slce []intvar arr[]intvar mp map[int]intvar cha chan intvar fn func()var ins interface{}fmt.Println(unsafe.Sizeof(ptr))fmt.Println(unsafe.Sizeof(slce))fmt.Println(unsafe.Sizeof(arr))fmt.Println(unsafe.Sizeof(mp))fmt.Println(unsafe.Sizeof(cha))fmt.Println(unsafe.Sizeof(fn))fmt.Println(unsafe.Sizeof(ins))
}
结果:
四、make和new关键字的区别
- 两个内置函数可以用来在堆上分配内存
- make只能用来分配和初始化slice、map、chan的类型的数据,而new可以分配任意类型的数据
- make分配数据内存返回的是引用,即Type,而new返回一个指向接收参数类型的指针,即*Type。(make 关键字的主要作用是创建 slice、map 和 Channel 等内置的数据结构,而 new 的主要作用是为类型申请一片内存空间,并返回指向这片内存的指针。
) - 接收参数个数不一样:make() 只接收一个参数,而 new() 可以接收多个参数
- make分配的空间后,会进行初始化。而new分配的空间会清零