Golang-Maaaap

下周出去玩.

从备忘里翻出一个话题. 也是曾经遇到的问题. 在这里记录一下, 希望能给他人帮助.

https://stackoverflow.com/questions/32751537/why-do-i-get-a-cannot-assign-error-when-setting-value-to-a-struct-as-a-value-i

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

基础真的很重要啊很重要

comments powered by Disqus