我们的业务代码中习惯使用 Map
维护一些 LocalCache
,前两天发现自己维护的一个 LocalCache
数据有些不对:Cache 的 Key
为某个对象的ID,值为这个ID对应的 PO
(即数据库中的对象),调试时发现所有的 Key
对应的值都是一样的,这是因为自己对一些细节没有关注到,还把 Java 那套东西搬来用导致的问题。
为了简化,我就不把业务代码搬上来了,写个简单的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| package main
import ( "encoding/json" "fmt" )
type Student struct { ID int Name string Age int }
func main() {
var students []Student students = append(students, Student{ ID: 1, Name: "张三", Age: 18, }) students = append(students, Student{ ID: 2, Name: "李四", Age: 19, }) students = append(students, Student{ ID: 3, Name: "王五", Age: 20, })
studentMap := make(map[int]*Student, len(students)) for _, student := range students { studentMap[student.ID] = &student }
bs, _ := json.Marshal(studentMap) fmt.Println(string(bs))
}
|
上边代码输出如下:
1
| {"1":{"ID":3,"Name":"王五","Age":20},"2":{"ID":3,"Name":"王五","Age":20},"3":{"ID":3,"Name":"王五","Age":20}}
|
可以看到所有的 value
是同一个 Student
,为什么会出现这样的问题呢?因为 students
存储的是 Student
的值,在给 for
循环中的 student
赋值时,是复制了一个新的值给它,而 for
循环中的 student
变量所指向的地址是不变的。
可以打印 student 的地址看一下:
1 2 3 4
| for _, student := range students { fmt.Printf("%p \n", &student) studentMap[student.ID] = &student }
|
输出为:
1 2 3 4
| 0xc0000a6040 0xc0000a6040 0xc0000a6040 {"1":{"ID":3,"Name":"王五","Age":20},"2":{"ID":3,"Name":"王五","Age":20},"3":{"ID":3,"Name":"王五","Age":20}}
|
这种情况下我们应该用 students
中索引对应数据的指针,上边 for 循环修改如下:
1 2 3 4
| for i, student := range students { fmt.Printf("%p \n", &students[i]) studentMap[student.ID] = &students[i] }
|
输出为:
1 2 3 4
| 0xc0000b8000 0xc0000b8020 0xc0000b8040 {"1":{"ID":1,"Name":"张三","Age":18},"2":{"ID":2,"Name":"李四","Age":19},"3":{"ID":3,"Name":"王五","Age":20}}
|
上边的情况给 student
赋值也是有问题的:
1 2 3 4 5 6
| for _, student := range students { student.Name = "test" }
bs, _ := json.Marshal(students) fmt.Println(string(bs))
|
输出:
1
| [{"ID":1,"Name":"张三","Age":18},{"ID":2,"Name":"李四","Age":19},{"ID":3,"Name":"王五","Age":20}]
|
Java 写习惯了就以为迭代时的 student
指向的是 students
中的地址。