golang 数据三 (字典)
发表于:2024-11-24 作者:热门IT资讯网编辑
编辑最后更新 2024年11月24日,golang基本数据结构Map也叫字典,字典的声明格式如下: map[KeyType]ValueType字典是无序键值对集合,字典要求KeyType必须是支持相等运算符(==,!=)的数据类型,比如:
golang基本数据结构Map也叫字典,字典的声明格式如下: map[KeyType]ValueType
字典是无序键值对集合,字典要求KeyType必须是支持相等运算符(==,!=)的数据类型,比如:数字、字符串、指针、数组、结构体以及对应的接口类型,而ValueType可以是任意类型,字典也是引用类型,使用make函数或者初始化表达式语句来创建。比如:
{ m := make(map[string]int) //创建一个字典 m["a"] = 1 m["b"] = 2 m2 := map[int]struct{ //匿名结构体 x int }{ 1: {x:100}, 2: {x:200}, } fmt.Println(m,m2)}
字典的基本操作
比如:
{ m := make(map[string]int) m["route"] = 66 //添加key i := m["route"] //读取key j := m["root"] //the value type is int, so the zero value is 0 n := len(m) //获取长度 //n := cap(m) // ??? 引发一个思考 delete(m, "route") //删除key}
如果访问不存在的键,不会引发错误,而会默认返回ValueType的零值,那么还能否根据零值来判断key的存在呢?
在读取map操作时,可以使用双返回值来正常读取map,比如:
{ j, ok := m["root"]}说明:ok是个bool类型变量,如果key真实存在,则ok的值为true,反之为false,如果你只是想测试key是否存在而不获取值的话,可以使用忽略字符"_"。
在修改map值操作时,因为受内存访问安全和哈希算法等缘故,字典被设计成"not adrressable",因此不能直接修改value成员(结构体或数组)。比如:
{ m := map[int] user { 1 : { name:"tom", age:19}, } m[1].age = 1 //cannot assign to struct field m[1].age in map}
但是有两种方式可以实现直接修改map的value成员:
是对整个value进行重新复制
声明map时valueType为指针类型
{ m := map[int] user { 1 : { name:"tom", age:19}, } u := m[1] u.age = 1 m[1] = u}{ m := map[int] *user { 1 : { name:"tom", age:19}, } m[1].age += 1}
不能对nil字典进行写操作,但是可以读,比如:
{ var m map[string]int //p := m["a"] //ok m["a"] = 1 //panic: assignment to entry in nil map}
map遍历:
{// var m = make(map[string]int) var m = map[string]int{} m["route"] = 66 m["root"] = 67 for key,value := range m{ fmt.Println("Key:", key, "Value:", value) }}
因为map是无序的,如果想按照有序key输出的话,可以先把所有的key取出,然后对key进行排序,再遍历map,比如:
{ m := make(map[int]int) var keys []int for i := 0 ;i <= 5;i++{ m[i] = i } for k, v := range m{ fmt.Println("Key:",k,"Value:",v) } for k := range m{ keys = append(keys, k) } sort.Ints(keys) for _, k := range keys{ fmt.Println("Key:",k,"Value:",m[k]) }}
并发
字典不是并发安全的数据结构,如果某个任务正在对字典进行写操作,那么其他任务就不能对该字典执行并发操作(读、写、删除),否则会导致程序崩溃,比如:
m := make(map[string]int) go func(){ for { m["a"] += 1 time.Sleep(time.Microsecond) } }() go func (){ for{ _ = m["b"] time.Sleep(time.Microsecond) } }() select{}}输出:fatal error: concurrent map read and map write
GO语言编译器提供了这种问题(竞争)的检测方式,比如:
# go run -race file.go
安全
可以使用 sync.RWMutex 实现同步,避免并发环境多goroutings同时读写操作,继续完善上面的例子,比如:
{ var lock = new(sync.RWMutex) m := make(map[string]int) go func(){ for { lock.Lock() m["a"]++ lock.Unlock() time.Sleep(time.Microsecond) } }() go func (){ for{ lock.RLock() _ = m["b"] lock.RUnlock() time.Sleep(time.Microsecond) } }() select{}}
性能
在创建字典时预先准备足够的空间有助于提升性能,减少扩张时引发内存动态分配和重复哈希操作,比如:
package mainimport "testing"import "fmt"func test() map[int]int { m := make(map[int]int) for i:=0; i < 1000; i++{ m[i] = 1 } return m}func testCap() map[int]int{ m := make(map[int]int,1000) for i:=0; i < 1000; i++{ m[i] = 1 } return m}func BenchmarkTest(t *testing.B){ for i:= 0;i < t.N; i++{ test() }}func BenchmarkTestCap(t *testing.B){ for i:= 0;i < t.N; i++{ testCap() }}func main(){ resTest := testing.Benchmark(BenchmarkTest) fmt.Printf("BenchmarkTest \t %d, %d ns/op,%d allocs/op, %d B/op\n", resTest.N, resTest.NsPerOp(), resTest.AllocsPerOp(), resTest.AllocedBytesPerOp()) resTest = testing.Benchmark(BenchmarkTestCap) fmt.Printf("BenchmarkTestCap \t %d, %d ns/op,%d allocs/op, %d B/op\n", resTest.N, resTest.NsPerOp(), resTest.AllocsPerOp(), resTest.AllocedBytesPerOp())}输出:# go run conmap.goBenchmarkTest 10000, 160203 ns/op,98 allocs/op, 89556 B/opBenchmarkTestCap 20000, 65478 ns/op,12 allocs/op, 41825 B/op
借鉴:<<雨痕笔记>>