Slice扩容机制

94 阅读3分钟

切片定义

type slice struct {
   array unsafe.Pointer
   len   int
   cap   int
}

切片是对数组的一个连续“片段”的引用,切片是引用类型;切片的出现,解决了数组的不足:固定的元素个数不可变和传值机制下导致的开销较大的问题;切片中可以通过内置函数 append,动态地向切片中添加元素。切片共三个字段,arraylencap,分别表示指向底层的数组指针、切片长度、切片容量即底层数组长度。

切片扩容

1.18之前,如果新申请的容量大于当前容量的2倍,则直接把申请的容量作为新的容量;如果申请的容量小于当前容量2倍,则当切片容量小于1024时,会直接把当前容量的2倍作为新的容量,当大于等于1024时,则会把当前容量的1.25倍作为新的容量。

1.18之后,如果新申请的容量大于当前容量的2倍,则直接把申请的容量作为新的容量;如果申请的容量小于当前容量2倍,则当切片容量小于256时,会直接把当前容量的2倍作为新的容量,当大于等于256时,则会把当前容量的1.25倍 + 192作为新的容量(192 = 256 * 3 / 4, newcap += (newcap + 3*threshold) / 4)

源码部分runtime/slice.go中的growslice(et *_type, old slice, cap int)

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
      }
   }
}

Slice扩容踩坑

func TestSlice(T *testing.T) {
   s := make([]int, 2, 2)
   s[0] = 10
   fmt.Printf("append s[0] is: %d\n", 10)
   fmt.Printf("slice s is: %v\n", s)
   s1 := s
   fmt.Printf("slice s1 is: %v\n", s1)
   s1[0] = 100
   fmt.Printf("append s1[0] is: %d\n", 100)
   fmt.Printf("slice s is: %v\n", s)
   fmt.Printf("slice s1 is: %v\n", s1)

   s[1] = 200
   fmt.Printf("append s1[1] is: %d\n", 200)
   fmt.Printf("slice s is: %v\n", s)
   fmt.Printf("slice s1 is: %v\n", s1)

   s1 = append(s1, 300)
   fmt.Printf("append s1 is: %d\n", 300)
   fmt.Printf("slice s is: %v\n", s)
   fmt.Printf("slice s1 is: %v\n", s1)

   s1[0] = -100
   fmt.Printf("append s1[0] is: %d\n", -100)
   fmt.Printf("slice s is: %v\n", s)
   fmt.Printf("slice s1 is: %v\n", s1)
}

上述代码最终输出为:

append s[0] is: 10
slice s is: [10 0]
slice s1 is: [10 0]
append s1[0] is: 100
slice s is: [100 0]
slice s1 is: [100 0]
append s1[1] is: 200
slice s is: [100 200]
slice s1 is: [100 200]
append s1 is: 300
slice s is: [100 200]
slice s1 is: [100 200 300]
append s1[0] is: -100
slice s is: [100 200]
slice s1 is: [-100 200 300]

可以看到最开始定义切片s初始化为长度和容量都是2,把s赋值给s1,之后对s1的前两次操作都修改了s,之后s1 = append(s1, 300)之后,对s1的修改并没有改变s。这是因为s1扩容了,指向底层数组的指针发生了改变。