Golang-Maaaap
下周出去玩.
从备忘里翻出一个话题. 也是曾经遇到的问题. 在这里记录一下, 希望能给他人帮助.
https://stackoverflow.com/questions/17438253/access-struct-in-map-without-copying
Problem
先来看一段小代码:
package main
import "fmt"
type person struct {
name string
age int
}
func main() {
m := map[int]person{}
for i := 0; i < 5; i++ {
m[i] = person{
name: fmt.Sprintf("person:%d", i),
}
}
for i := 0; i < 5; i++ {
m[i].age = i
}
for _, p := range m {
fmt.Println(p.name, p.age)
}
}
https://play.golang.org/p/GD3AnjclSiw
然后我们得到了一个错误:
prog.go:20:12: cannot assign to struct field m[i].age in map
golang新手可能会遇到这样的问题, 但表面上看好像没什么毛病. 给map中的一个对象的某个属性赋值, 有问题吗?
有的.
如果你数据结构基础还在的话, 就会想到 map 其实就是哈希表, 而哈希表是一直在变的, 它不能保证当你存入或者取出数据之后, 哈希表中的data还在原来的位置, 因为每次都可能会涉及到重新分配地址, 而在map中的对象, 也就可能不在原来的地方了, map[i]
是一个person
的结构, 取出来的时候内存地址为 0xabcdefg
但是如果想给它赋值, 找他的时候, 内存中的地址就可能会变成 0x1234567
, 也就是说, 这个person一直在变, 你无法获得他的地址, 也就没办法给他的某一个属性赋值.
当然, 你可以把整个对象替换掉. 这就相当于重新给map的key存了一个值.
Solution
Pointer Value
既然我们无法获取到map的某个key对应的值的地址, 那么, 如果这个值就是一个地址呢?
比如, map[int]*person
:
package main
import "fmt"
type person struct {
name string
age int
}
func main() {
m := map[int]*person{}
for i := 0; i < 5; i++ {
m[i] = &person{
name: fmt.Sprintf("person:%d", i),
}
}
for i := 0; i < 5; i++ {
m[i].age = i
}
for _, p := range m {
fmt.Println(p.name, p.age)
}
}
https://play.golang.org/p/rwzmap72kTI
这里其实就是让value可以被addressable, 然后就能对其属性重新赋值了.
Re-Put Value
如果你的业务不允许用指针作为value(出于xxx的原因和考虑), 那么可以用下面这种方法, 先把value取出来, 然后对value进行修改, 最后把value重新放进去(有点类似把大象装进冰箱):
package main
import "fmt"
type person struct {
name string
age int
}
func main() {
m := map[int]person{}
for i := 0; i < 5; i++ {
m[i] = person{
name: fmt.Sprintf("person:%d", i),
}
}
for i := 0; i < 5; i++ {
pi := m[i]
pi.age = i
m[i] = pi
}
for _, p := range m {
fmt.Println(p.name, p.age)
}
}
https://play.golang.org/p/OFIiXAUkGOq
More
利用这种方法, 我们可以很方便的做一个map的原子计数器:
var counter = map[string]*uint64
针对不同IP的锁:
var clientLocker = map[string]*sync.Mutex
或者给每个user一个limtter:
var clientLimiter = map[string]*RateLimtter
基础真的很重要啊很重要