分享
  1. 首页
  2. 文章

golang slice 使用及源码分析

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

  • 1.先做个小实验
func main(){
 s1:=make([]int,0,10)
 s1=[]int{1,2,3}
 ss:=make([]int,0,10)
 ss = s1[1:]
 for i:=0;i<len(ss);i++{
 ss[i] +=10
 }
 fmt.Println(s1) // [1 12 13]
 ss =append(ss,4)
 for i:=0;i<len(ss);i++{
 ss[i] +=10
 }
 fmt.Println(s1) // [1 12 13] 而不是 [1,22,23]

 t:=[]int{0}
 printPoint(t) // 0xc4200140a8 cap(s)= 1
 t = append(t,1)
 printPoint(t) // 0xc4200140c0 0xc4200140c8 cap(s)= 2
 t = append(t,2)
 printPoint(t) // 0xc4200160e0 0xc4200160e8 0xc4200160f0 cap(s)= 4
 t = append(t,3)
 printPoint(t) // 0xc4200160e0 0xc4200160e8 0xc4200160f0 0xc4200160f8 cap(s)= 4
}
func printPoint(s []int){
 for i:=0;i<len(s);i++{
 fmt.Print(unsafe.Pointer(&s[i])," ")
 }
 fmt.Println("cap(s)=",cap(s))
}

发现slice在进行append操作时会跟据原来的slice容量,如果append完成后新slice的容量超过原来slice的容量,则需要扩容,并且将旧的slice数据全部迁移到新的slice开辟的地址里。

  • 2.在runtime目录下找到slice.go,定位到growslice(et *_type, old slice, cap int)这个函数
type slice struct {
 array unsafe.Pointer
 len int
 cap int
}
// growslice handles slice growth during append.
// It is passed the slice element type, the old slice, and the desired new minimum capacity,
// and it returns a new slice with at least that capacity, with the old data
// copied into it.
// The new slice's length is set to the old slice's length,
// NOT to the new requested capacity.
// This is for codegen convenience. The old slice's length is used immediately
// to calculate where to write new values during an append.
// TODO: When the old backend is gone, reconsider this decision.
// The SSA backend might prefer the new length or to return only ptr/cap and save stack space.
// 与append(slice,s)对应的函数growslice
// 通过切片的类型,旧切片的容量和数据得出新切片的容量,新切片跟据容量重新申请一块地址,把旧切片的数据拷贝到新切片中
func growslice(et *_type, old slice, cap int) slice {
// 单纯地扩容,不写数据
 if et.size == 0 {
 if cap < old.cap {
 panic(errorString("growslice: cap out of range"))
 }
 // append should not create a slice with nil pointer but non-zero len.
 // We assume that append doesn't need to preserve old.array in this case.
 return slice{unsafe.Pointer(&zerobase), old.len, cap}
 }
// 扩容规则 1.新的容量大于旧的2倍,直接扩容至新的容量
// 2.新的容量不大于旧的2倍,当旧的长度小于1024时,扩容至旧的2倍,否则扩容至旧的5/4倍
 newcap := old.cap
 doublecap := newcap + newcap
 if cap > doublecap {
 newcap = cap
 } else {
 if old.len < 1024 {
 newcap = doublecap
 } else {
 for newcap < cap {
 newcap += newcap / 4
 }
 }
 }
// 跟据切片类型和容量计算要分配内存的大小
 var lenmem, newlenmem, capmem uintptr
 const ptrSize = unsafe.Sizeof((*byte)(nil))
 switch et.size {
 case 1:
 lenmem = uintptr(old.len)
 newlenmem = uintptr(cap)
 capmem = roundupsize(uintptr(newcap))
 newcap = int(capmem)
 case ptrSize:
 lenmem = uintptr(old.len) * ptrSize
 newlenmem = uintptr(cap) * ptrSize
 capmem = roundupsize(uintptr(newcap) * ptrSize)
 newcap = int(capmem / ptrSize)
 default:
 lenmem = uintptr(old.len) * et.size
 newlenmem = uintptr(cap) * et.size
 capmem = roundupsize(uintptr(newcap) * et.size)
 newcap = int(capmem / et.size)
 }
// 异常情况,旧的容量比新的容量还大或者新的容量超过限制了
 if cap < old.cap || uintptr(newcap) > maxSliceCap(et.size) {
 panic(errorString("growslice: cap out of range"))
 }
 var p unsafe.Pointer
 if et.kind&kindNoPointers != 0 {
// 为新的切片开辟容量为capmem的地址空间
 p = mallocgc(capmem, nil, false)
// 将旧切片的数据搬到新切片开辟的地址中
 memmove(p, old.array, lenmem)
 // The append() that calls growslice is going to overwrite from old.len to cap (which will be the new length).
 // Only clear the part that will not be overwritten.
// 清理下新切片中剩余地址,不能存放堆栈指针
// memclrNoHeapPointers clears n bytes starting at ptr.
//
// Usually you should use typedmemclr. memclrNoHeapPointers should be
// used only when the caller knows that *ptr contains no heap pointers
// because either:
//
// 1. *ptr is initialized memory and its type is pointer-free.
//
// 2. *ptr is uninitialized memory (e.g., memory that's being reused
// for a new allocation) and hence contains only "junk".
 memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
 } else {
 // Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory.
 p = mallocgc(capmem, et, true)
 if !writeBarrier.enabled {
 memmove(p, old.array, lenmem)
 } else {
 for i := uintptr(0); i < lenmem; i += et.size {
 typedmemmove(et, add(p, i), add(old.array, i))
 }
 }
 }
 return slice{p, old.len, newcap}
}
  • 3.总结
  1. 不要轻易的对切片append,如果新的切片容量比旧的大的话,需要进行growslice操作,新的地址开辟,数据拷贝
  2. 尽量对切片设置初始容量值以避免growslice,类似make([]int,0,100)
  3. 切片是一个结构体,保存着切片的容量,实际长度以及数组的地址

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

本文来自:博客园

感谢作者:fwdqxl

查看原文:golang slice 使用及源码分析

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

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

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏