slice的append操作注意事项

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

如果不能很好的理解slice和slice的append操作,把slice作为引用类型传参,会导致丢失数据
先上一个例子:

package main

import "fmt"

type DbItem struct {
    Id  int16
    Cnt int32
}

func combineItem(itemList []DbItem, id int16, cnt int32) {
    item := DbItem{Id: int16(id), Cnt: int32(cnt)}
    itemList = append(itemList, item)
    fmt.Printf("combineItem itemList addr[%p]  cap[%d] len[%d] values: %v \n", itemList, cap(itemList), len(itemList), itemList)
}

func main() {
    itemList := make([]DbItem, 1, 5)
    combineItem(itemList, int16(1), int32(2))
    fmt.Printf("1.cap没有扩容的情况 itemList addr[%p] cap[%d] len[%d] values: %v \n", itemList, cap(itemList), len(itemList), itemList)
    itemList = make([]DbItem, 5, 5)
    combineItem(itemList, int16(1), int32(2))
    fmt.Printf("2.cap被扩容,新DbItem追加在slice最后 itemList addr[%p] cap[%d] len[%d] values: %v \n", itemList, cap(itemList), len(itemList), itemList)
    itemList = make([]DbItem, 5, 5)
    combineItem(itemList[:1], int16(1), int32(2))
    fmt.Printf("3.cap没被扩容,新DbItem追加在slice中间 itemList addr[%p] cap[%d] len[%d] values: %v \n", itemList, cap(itemList), len(itemList), itemList)
}

运行结果:
combineItem itemList addr[0xc4200480f0] cap[5] len[2] values: [{0 0} {1 2}]
1.cap没有扩容的情况 itemList addr[0xc4200480f0] cap[5] len[1] values: [{0 0}]
combineItem itemList addr[0xc42003e050] cap[10] len[6] values: [{0 0} {0 0} {0 0} {0 0} {0 0} {1 2}]
2.cap被扩容,新DbItem追加在slice最后 itemList addr[0xc420048120] cap[5] len[5] values: [{0 0} {0 0} {0 0} {0 0} {0 0}]
combineItem itemList addr[0xc420048150] cap[5] len[2] values: [{0 0} {1 2}]
3.cap没被扩容,新DbItem追加在slice中间 itemList addr[0xc420048150] cap[5] len[5] values: [{0 0} {1 2} {0 0} {0 0} {0 0}]

当slice_c=append(slice_a,slice_b)中len(slice_a)+len(slice_b)<=cap(slice_a)时不会扩容,否则会被扩为cap(slice_a)的两倍,如果slice len大于1024了,会扩容四分之一cap。具体实现源码如下:

newcap := old.cap
if newcap+newcap < cap {
    newcap = cap
} else {
    for {
        if old.len < 1024 {
            newcap += newcap
        } else {
            newcap += newcap / 4
        }
        if newcap >= cap {
            break
        }
    }
}

从例2可以看见,append返回的slice指向了个新的地址。但是即使没扩容,slice指向同一个地址的情况下,为何slice_c还是不等于slice_a呢?其实slice不是单纯一个指针,而是个C的struct实现的:

struct    Slice
{    // must not move anything
    byte*    array;        // actual data
    uintgo    len;        // number of elements
    uintgo    cap;        // allocated number of elements
};

在$GOROOT/src/pkg/runtime/runtime.h可以看见源码。
在没扩容的情况下,传入slice_a的数据改变是会影响函数外的slice_a的值的,因为他们的byte*指向同一个地址。
扩容的情况下就没有这个影响了。无论扩容与否,slice的值还取决于len,所以即使slice的byte*指向同一地址,打出来的结果还是不一样的。所以当函数里面有append,千万别把slice作为引用类型的参数用了!


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

本文来自:Segmentfault

感谢作者:Cedrus

查看原文:slice的append操作注意事项

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

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