问题
不断将获取的信息存储进切片slice中,为了方便更新信息中的某些字段,建立了一个key和切片项地址的map,一边在切片中存储信息,一边建立Key-Value关系,导致通过key取到的值并不正确,也导致需要更新的字段没有更新,代码类似于:
var dataSlice []interface{}
var keyToData = make(map[string]interface{})
var id = 0
for info := fetchInfo(key) {
dataSlice = append(dataSlice, info)
// keyToData[key] = &info
keyToData[key] = &dataSlice[id]
id++
}
info := keyToData[key] // 获取的info不是正确的data,更新的信息同步到dataSlice中
分析
map中保存的指针没有指向dataSlice中的项
测试
type test struct {
ID string
}
func main() {
strArr := []string{}
testArr := []test{}
intArr := []int{}
strA := "info"
strB := "mation"
fmt.Printf("string: \t A: %p, B: %p, Arr: %v, ArrPtr: %p\n", &strA, &strB, strArr, &strArr)
strArr = append(strArr, strA)
fmt.Printf("string array: \t arr: %v, pointer: %p, A: %p\n", strArr, &strArr, &strArr[0])
strArr = append(strArr, strB)
fmt.Printf("string array: \t arr: %v, pointer: %p, A: %p, B: %p\n\n\n\n", strArr, &strArr, &strArr[0], &strArr[1])
testA := test{ID: strA}
testB := test{ID: strB}
fmt.Printf("test: \t A: %p, B: %p, Arr: %v, ArrPtr: %p\n", &testA, &testB, testArr, &testArr)
testArr = append(testArr, testA)
fmt.Printf("test array: \t arr: %v, pointer: %p, A: %p\n", testArr, &testArr, &testArr[0])
testArr = append(testArr, testB)
fmt.Printf("test array: \t arr: %v, pointer: %p, A: %p, B: %p\n\n\n\n", testArr, &testArr, &testArr[0], &testArr[1])
intA := 0
intB := 1
fmt.Printf("int: \t A: %p, B: %p, Arr: %v, ArrPtr: %p\n", &intA, &intB, intArr, &intArr)
intArr = append(intArr, intA)
fmt.Printf("int array: \t arr: %v, pointer: %p, A: %p\n", intArr, &intArr, &intArr[0])
intArr = append(intArr, intB)
fmt.Printf("int array: \t arr: %v, pointer: %p, A: %p, B: %p\n\n\n\n", intArr, &intArr, &intArr[0], &intArr[1])
}
输出结果
string: A: 0xc42000e1f0, B: 0xc42000e200, Arr: [], ArrPtr: 0xc42000a060
string array: arr: [info], pointer: 0xc42000a060, A: 0xc42000e210
string array: arr: [info mation], pointer: 0xc42000a060, A: 0xc42000a100, B: 0xc42000a110
test: A: 0xc42000e250, B: 0xc42000e260, Arr: [], ArrPtr: 0xc42000a080
test array: arr: [{info}], pointer: 0xc42000a080, A: 0xc42000e270
test array: arr: [{info} {mation}], pointer: 0xc42000a080, A: 0xc42000a180, B: 0xc42000a190
int: A: 0xc420014068, B: 0xc420014080, Arr: [], ArrPtr: 0xc42000a0a0
int array: arr: [0], pointer: 0xc42000a0a0, A: 0xc420014088
int array: arr: [0 1], pointer: 0xc42000a0a0, A: 0xc4200140a0, B: 0xc4200140a8
讨论
- 需要注意的是,通过append在切片slice中存储时,进行的是值传递;
- 每次调用append时,都可能会造成整个切片存储内存上的重新调整,通过元素地址可以反映出,每次append时,golang都会尽力保证一段连续的内存进行元素存储;
- 所以前面map保存的元素地址,可能不会指向最终slice中的元素;
- 不管内存上如何调整,都保证了切片入口地址的不变,而且这个地址并不等同于第一个元素的地址,所以这个节点应该和C链表中头节点的功能类似;
- 那么如果通过make创建定长的切片,这时的切片性质上和数组等同,请看补充测试:
补充测试:
test2Arr := make([]test, 2, 2)
test3Arr := make([]interface{}, 2, 2)
var test4Arr [2]interface{}
fmt.Printf("test 2: \t A: %p, B: %p, Arr: %v, ArrPtr: %p\n", &testA, &testB, test2Arr, &test2Arr)
test2Arr = append(test2Arr, testA)
fmt.Printf("test 2 array: \t arr: %v, pointer: %p, A: %p\n", test2Arr, &test2Arr, &test2Arr[0])
test2Arr = append(test2Arr, testB)
fmt.Printf("test 2 array: \t arr: %v, pointer: %p, A: %p, B: %p\n\n\n\n", test2Arr, &test2Arr, &test2Arr[0], &test2Arr[1])
fmt.Printf("test 3: \t A: %p, B: %p, Arr: %v, ArrPtr: %p\n", &testA, &testB, test3Arr, &test3Arr)
test3Arr[0] = &testA
fmt.Printf("test 3 array: \t arr: %v, pointer: %p, A: %p\n", test3Arr, &test3Arr, test3Arr[0])
test3Arr[1] = &testB
fmt.Printf("test 3 array: \t arr: %v, pointer: %p, A: %p, B: %p\n\n\n\n", test3Arr, &test3Arr, test3Arr[0], test3Arr[1])
fmt.Printf("test 4: \t A: %p, B: %p, Arr: %v, ArrPtr: %p\n", &testA, &testB, test4Arr, &test4Arr)
test4Arr[0] = &testA
fmt.Printf("test 3 array: \t arr: %v, pointer: %p, A: %p\n", test4Arr, &test4Arr, test4Arr[0])
test4Arr[1] = &testB
fmt.Printf("test 3 array: \t arr: %v, pointer: %p, A: %p, B: %p\n\n\n\n", test4Arr, &test4Arr, test4Arr[0], test4Arr[1])
结果输出
test 2: A: 0xc420066220, B: 0xc420066230, Arr: [{} {}], ArrPtr: 0xc420086080
test 2 array: arr: [{} {} {info}], pointer: 0xc420086080, A: 0xc420072080
test 2 array: arr: [{} {} {info} {mation}], pointer: 0xc420086080, A: 0xc420072080, B: 0xc420072090
test 3: A: 0xc420066220, B: 0xc420066230, Arr: [<nil> <nil>], ArrPtr: 0xc4200860c0
test 3 array: arr: [0xc420066220 <nil>], pointer: 0xc4200860c0, A: 0xc420066220
test 3 array: arr: [0xc420066220 0xc420066230], pointer: 0xc4200860c0, A: 0xc420066220, B: 0xc420066230
test 4: A: 0xc420066220, B: 0xc420066230, Arr: [<nil> <nil>], ArrPtr: 0xc420086100
test 3 array: arr: [0xc420066220 <nil>], pointer: 0xc420086100, A: 0xc420066220
test 3 array: arr: [0xc420066220 0xc420066230], pointer: 0xc420086100, A: 0xc420066220, B: 0xc420066230
结论
- test 2 验证了如果make定长的切片,即使通过append添加存储,也不会引起元素内存的重新分配
- test 3 和test 4 验证了make定长的切片和数组等同;
- 虽然性质等同,但是append还是可以为slice添加元素,通过下标进行赋值时,不能进行容量扩展,报越界错误。
@author liu shuohui
@date 2019-05-23