Golang切片slice存储微探索

树袋Papa · · 504 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

问题

不断将获取的信息存储进切片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

讨论

  1. 需要注意的是,通过append在切片slice中存储时,进行的是值传递;
  2. 每次调用append时,都可能会造成整个切片存储内存上的重新调整,通过元素地址可以反映出,每次append时,golang都会尽力保证一段连续的内存进行元素存储;
  3. 所以前面map保存的元素地址,可能不会指向最终slice中的元素;
  4. 不管内存上如何调整,都保证了切片入口地址的不变,而且这个地址并不等同于第一个元素的地址,所以这个节点应该和C链表中头节点的功能类似;
  5. 那么如果通过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

结论

  1. test 2 验证了如果make定长的切片,即使通过append添加存储,也不会引起元素内存的重新分配
  2. test 3 和test 4 验证了make定长的切片和数组等同;
  3. 虽然性质等同,但是append还是可以为slice添加元素,通过下标进行赋值时,不能进行容量扩展,报越界错误。

@author liu shuohui
@date 2019-05-23


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:树袋Papa

查看原文:Golang切片slice存储微探索

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

504 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传