上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
5.4 字典
字典(哈希表)存储键值对,一种使用频率极高的数据结构。
pointer header +-------+ +-----------+ | map -|--------> | hmap | +-------+ +-----------+ | ... | +-----------+ +-----//-----+ | buckets -|-----> | ... ... | array +-----------+ +-----//-----+
- 引用类型,无序键值对集合。
- 以初始化表达式或
make
函数创建。
- 主键须是支持
==
、!=
的类型。 - 可判断字典是否为
nil
,不支持比较操作。
- 函数
len
返回键值对数量。 - 访问不存在主键,返回零值。
- 迭代以随机次序返回。
- 值不可寻址(not addressable),需整体赋值。
- 按需扩张,但不会收缩(shrink)内存。
func p(m map[string]int) { fmt.Printf("%t, %x\n", m == nil, *(*uintptr)(unsafe.Pointer(&m))) } func main() { // 字典变量是指针类型。 // 未初始化,那就是空指针。 var m1 map[string]int m2 := map[string]int{} m3 := make(map[string]int, 0) p(m1) p(m2) p(m3) } /* m1: true, 0 m2: false, c000064da0 m3: false, c000064d70 */
func main() { m1 := map[string]int { "a": 1, "b": 2, } // 省略复合类型标签。 m2 := map[string]struct { id int name string }{ "a": { 1, "u1" }, "b": { 2, "u2" }, } // 分配足够初始容量,可减少后续扩张。 m3 := make(map[string]int, 10) m3["a"] = 1 fmt.Println(m1, len(m1)) fmt.Println(m2, len(m2)) fmt.Println(m3, len(m3)) } /* m1: map[a:1 b:2] len = 2 m2: map[a:{1 u1} b:{2 u2}] len = 2 m3: map[a:1] 1 len = 1 */
func main() { m1 := map[int]int{} m2 := map[int]int{} // _ = m1 == m2 // ~~~~~~~~ invalid: map can only be compared to nil }
- 建议以
ok-idiom
访问主键,确认是否存在。 - 删除(
delete
)不存在键值,不会引发错误。 - 清空(
clear
)字典,不会收缩内存。 - 可对
nil
字典读、删除,但写会引发panic
!
func main() { m := map[string]int{} // 是否有 { "a": 0 } 存在? x := m["a"] fmt.Println(x) // 0 x, ok := m["a"] fmt.Println(x, ok) // 0 false m["a"] = 0 x, ok = m["a"] fmt.Println(x, ok) // 0 true }
func main() { var m map[string]int // nil _ = m["a"] delete(m, "a") // m["a"] = 0 // ~~~~~~ panic: assignment to entry in nil map }
func main() { m := map[string]int { "a": 1, "b": 2, } println(len(m)) // 2 clear(m) println(len(m)) // 0 }
迭代字典,以 “随机” 次序返回键值。
- 以
struct{}
为可忽略值类型。 - 随机效果依赖键值对数量和迭代次数。
- 可用作简易版随机挑选算法。
func main() { m := make(map[int]struct{}) for i := 0; i < 10; i++ { m[i] = struct{}{} } for i := 0; i < 4; i++ { for k, _ := range m { print(k, ",") } println() } } /* 7,2,3,4,5,9,0,1,6,8, 1,6,8,9,0,3,4,5,7,2, 0,1,6,8,9,2,3,4,5,7, 9,0,1,6,8,7,2,3,4,5, */
因 内联存储 、 键值迁移 及 内存安全 需要,字典被设计成不可寻址。
不能直接修改值成员(结构或数组),应整体赋值,或以指针代替。
内联存储有更好的性能,减少 GC 压力。详情请参考《下卷:8.4 字典》。
func main() { type user struct { name string age byte } // 指针。 m := map[int]*user{ 1: &user{"u1", 19}, } // 返回指针,修改外部对象。 m[1].age = 20 }
func main() { m := map[string]int{ "a": 1 } m["a"]++ // m["a"] = m["a"] + 1 }
安全
迭代期间,新增或删除操作是安全的,但无法控制次序。
func main() { m := make(map[int]int) for i := 0; i < 10; i++ { m[i] = i + 10 } for k := range m { if k == 5 { m[100] = 1000 } delete(m, k) fmt.Println(k, m) } }
运行时会对字典并发操作做出检测。
- 启用竞争检测(data race)查找此类问题。
- 使用
sync.Map
代替。
package main func main() { m := make(map[string]int) // write go func() { for { m["a"] += 1 } }() // read go func() { for { _ = m["b"] } }() select {} } // fatal error: concurrent map read and map write
$ go build -race && ./test ================== WARNING: DATA RACE Write at 0x00c000078150 by goroutine 7: main.go:9 +0x7b Previous read at 0x00c000078150 by goroutine 8: main.go:16 +0x44 Goroutine 7 (running) created at: main.main() main.go:7 +0x84 Goroutine 8 (running) created at: main.main() main.go:14 +0xd4 ==================
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论