切片扩容源码阅读

1、切片扩容源代码如下:

func growslice(et *_type, old slice, cap int) slice {
   if raceenabled {
      callerpc := getcallerpc()
      racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, abi.FuncPCABIInternal(growslice))
   }
   if msanenabled {
      msanread(old.array, uintptr(old.len*int(et.size)))
   }
   if asanenabled {
      asanread(old.array, uintptr(old.len*int(et.size)))
   }

   if cap < old.cap {
      panic(errorString("growslice: cap out of range"))
   }

   if et.size == 0 {
      // 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}
   }

   newcap := old.cap
   doublecap := newcap + newcap
   if cap > doublecap {
      newcap = cap
   } else {
      const threshold = 256
      if old.cap < threshold {
         newcap = doublecap
      } else {
         // Check 0 < newcap to detect overflow
         // and prevent an infinite loop.
         for 0 < newcap && newcap < cap {
            // Transition from growing 2x for small slices
            // to growing 1.25x for large slices. This formula
            // gives a smooth-ish transition between the two.
            newcap += (newcap + 3*threshold) / 4
         }
         // Set newcap to the requested cap when
         // the newcap calculation overflowed.
         if newcap <= 0 {
            newcap = cap
         }
      }
   }

   var overflow bool
   var lenmem, newlenmem, capmem uintptr
   // Specialize for common values of et.size.
   // For 1 we don't need any division/multiplication.
   // For goarch.PtrSize, compiler will optimize division/multiplication into a shift by a constant.
   // For powers of 2, use a variable shift.
   switch {
   case et.size == 1:
      lenmem = uintptr(old.len)
      newlenmem = uintptr(cap)
      capmem = roundupsize(uintptr(newcap))
      overflow = uintptr(newcap) > maxAlloc
      newcap = int(capmem)
   case et.size == goarch.PtrSize:
      lenmem = uintptr(old.len) * goarch.PtrSize
      newlenmem = uintptr(cap) * goarch.PtrSize
      capmem = roundupsize(uintptr(newcap) * goarch.PtrSize)
      overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize
      newcap = int(capmem / goarch.PtrSize)
   case isPowerOfTwo(et.size):
      var shift uintptr
      if goarch.PtrSize == 8 {
         // Mask shift for better code generation.
         shift = uintptr(sys.Ctz64(uint64(et.size))) & 63
      } else {
         shift = uintptr(sys.Ctz32(uint32(et.size))) & 31
      }
      lenmem = uintptr(old.len) << shift
      newlenmem = uintptr(cap) << shift
      capmem = roundupsize(uintptr(newcap) << shift)
      overflow = uintptr(newcap) > (maxAlloc >> shift)
      newcap = int(capmem >> shift)
   default:
      lenmem = uintptr(old.len) * et.size
      newlenmem = uintptr(cap) * et.size
      capmem, overflow = math.MulUintptr(et.size, uintptr(newcap))
      capmem = roundupsize(capmem)
      newcap = int(capmem / et.size)
   }

   // The check of overflow in addition to capmem > maxAlloc is needed
   // to prevent an overflow which can be used to trigger a segfault
   // on 32bit architectures with this example program:
   //
   // type T [1<<27 + 1]int64
   //
   // var d T
   // var s []T
   //
   // func main() {
   //   s = append(s, d, d, d, d)
   //   print(len(s), "\n")
   // }
   if overflow || capmem > maxAlloc {
      panic(errorString("growslice: cap out of range"))
   }

   var p unsafe.Pointer
   if et.ptrdata == 0 {
      p = mallocgc(capmem, nil, false)
      // 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(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 lenmem > 0 && writeBarrier.enabled {
         // Only shade the pointers in old.array since we know the destination slice p
         // only contains nil pointers because it has been cleared during alloc.
         bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem-et.size+et.ptrdata)
      }
   }
   memmove(p, old.array, lenmem)

   return slice{p, old.len, newcap}
}

一步步来分析:

  1. 在使用append函数对切片追加元素时,要是容量不够,仍然要追加元素,append函数就会调用growslice函数来进行扩容。
  2. *_type是一个结构体,结构体内容如下:
type _type struct {
	size       uintptr
	ptrdata    uintptr // size of memory prefix holding all pointers
	hash       uint32
	tflag      tflag
	align      uint8
	fieldAlign uint8
	kind       uint8
	// function for comparing objects of this type
	// (ptr to object A, ptr to object B) -> ==?
	equal func(unsafe.Pointer, unsafe.Pointer) bool
	// gcdata stores the GC type data for the garbage collector.
	// If the KindGCProg bit is set in kind, gcdata is a GC program.
	// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
	gcdata    *byte
	str       nameOff
	ptrToThis typeOff
}
  1. slice也同样是一个结构体,结构体内容如下:
type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

可以看到slice结构体又3个字段组成,分别是array、len、cap。array是任意类型的指针变量,len是int类型的变量,cap同样是int类型的变量。在growslice函数中,通过old可以调用slice结构体中的字段。

  1. cap是int类型的数据,cap是新申请容量。
  2. 前面的if条件可以先省略,从此处开始看:
  newcap := old.cap
   doublecap := newcap + newcap
   if cap > doublecap {
      newcap = cap
   } else {
      const threshold = 256
      if old.cap < threshold {
         newcap = doublecap
      } else {
         // Check 0 < newcap to detect overflow
         // and prevent an infinite loop.
         for 0 < newcap && newcap < cap {
            // Transition from growing 2x for small slices
            // to growing 1.25x for large slices. This formula
            // gives a smooth-ish transition between the two.
            newcap += (newcap + 3*threshold) / 4
         }
         // Set newcap to the requested cap when
         // the newcap calculation overflowed.
         if newcap <= 0 {
            newcap = cap
         }
      }
   }

将旧容量的值,赋值给变量newcap,该变量是最终容量。将newcap的值成倍增加,并赋值给变量doublecap。如果新申请的容量大于doublecap,将新申请的容量cap值,赋值给最终容量newcap。否则,来到else分支。先定义一个常量256,以后threshold便代表256。如果旧容量小于256,将doublecap的值赋值给变量newcap。否则,来到else分支。for循环判断条件,0<newcap&&newcap<cap,最终容量大于0小于新申请容量。将开始执行newcap += (newcap + 3*threshold)/4,该公式在小切片时,最终容量为2倍,大切片时,容量为1.25倍。for循环退出后,判断newcap是否小于等于0。如果是,则将新申请容量的值,赋值给最终容量newcap。此处是slice扩容的核心代码
可以看最后的返回值,仍是一个切片。代码如下:

return slice{p, old.len, newcap}

将新切片返回,并且长度为旧切片长度加添加的元素,容量为最终容量newcap。

2、总结

总的来说,切片扩容很复杂,本文只是作者对切片扩容的简单理解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值