三.slice切片的底层

三.slice切片的底层

对应问题

切片底层(跟谁学,好未来,伴鱼,知乎,七牛+3,京东,哔哩哔哩,腾讯+1,小米,字节,Aibee,网易)

slice,len,cap,共享,扩容(腾讯)

append底层 (七牛)

1.切片的数据结构

img

由图可以看到切片的底层实际上是一个数据结构,包含指向数组的指针,len,cap

len是切片可以访问的长度,当用for遍历的时候,打印出来的个数就是len的长度

cap最大可以是底层数组的容量-array指针的位置,当底层数组容量不够的时候,将进行扩容

并且两个切片还允许指向同一个底层数组,这个时候如果对一个切片进行操作,会改变另外一个切片的值**(共享存储空间)**

func TestTwoSliceChange(t *testing.T) {
   array := [6]int{10, 20, 30, 40, 50, 60}
   sliceA := array[2:5:5]

   sliceB := array[1:3:5]

   fmt.Println(sliceA)
   fmt.Println(sliceB)
   
   sliceA[0] = 999

   fmt.Println(sliceA)
   fmt.Println(sliceB)
   /*打印结果
   [30 40 50]
   [20 30]
   [999 40 50]
   [20 999]
   */

}

2.切片的扩容(1.18)

go的切片扩容机制(小米+1, 腾讯,知乎,字节,伴鱼)

在以前的版本中:

  • 如果新申请容量比两倍原有容量大,那么扩容后容量大小 等于 新申请容量

  • 如果原有 slice 长度小于 1024, 那么每次就扩容为原来的 2 倍

  • 如果原 slice 大于等于 1024, 那么每次扩容就扩为原来的 1.25 倍

在1.18的版本中:

  • 如果新申请容量比两倍原有容量大,那么扩容后容量大小等于新申请容量
  • 如果原有slice长度小于256,那么每次就扩容为原来的2倍
  • 如果原有slice大于等于256,那么每次扩容就扩为原来的1.25倍+3/4*256(也就是1.25倍再加192)

测试代码如下

//测试cap的变化规律
//在512之前都是两倍的增长
func TestCap(t *testing.T) {
   var r = 10000
   arr := make([]int, 0, 1)
   c := cap(arr)
   for i := 0; i < r; i++ {
      arr = append(arr, 1)
      if n := cap(arr); n != c {
         fmt.Printf("%d->%d %f倍\n", i, n, float64(n)/float64(i))
         c = n
      }
   }
}

扩容 源代码在 go/src/slice.go中

func growslice(et *_type, old slice, cap int) slice {
    
    //主要代码
  	
   newcap := old.cap
   doublecap := newcap + newcap
   //如果cap比两倍还大,直接将新容量等于cap

   if cap > doublecap {
      newcap = cap
   } else {
   //否则进行扩容
      const threshold = 256
      //如果旧容量小于256,直接进行翻倍
       if old.cap < threshold {
         newcap = doublecap
      } else {
          //否则进行一个for循环进行扩容
         //扩容策略是
         // 检查 0 < newcap 去预防 扩容溢出和无限的循环
         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.
             //从两倍扩容变成一个1.25倍扩容然后再加192,这个函数给了两者一个平滑的过渡()
            newcap += (newcap + 3*threshold) / 4
         }
         // Set newcap to the requested cap when
         // the newcap calculation overflowed.
           //如果扩容会导致溢出,那直接将新容量等于cap
         if newcap <= 0 {
            newcap = cap
         }
      }
   }

但是就这样结束了吗,我们将结果打印出来

1->2 2.000000倍
2->4 2.000000倍
4->8 2.000000倍
8->16 2.000000倍
16->32 2.000000倍
32->64 2.000000倍
64->128 2.000000倍
128->256 2.000000倍
256->512 2.000000倍
512->848 1.656250倍
848->1280 1.509434倍
1280->1792 1.400000倍
1792->2560 1.428571倍
2560->3408 1.331250倍
3408->5120 1.502347倍
5120->7168 1.400000倍
7168->9216 1.285714倍
9216->12288 1.333333倍

我们主要看256这个临界点的值

256*1.25+192=512 确实没什么问题

但是下一个就出现问题了

512*1.25+192=832

答案应该是832才对,但是扩容的结果确实848。

后来才发现扩容源码不仅这点,下面的操作也会对容量进行改变

//关键代码
//计算新的容量所需要的空间
newlenmem = uintptr(cap) * goarch.PtrSize
//关键这步,内存页对齐,golang 中pagesize 为8kB
capmem = roundupsize(uintptr(newcap) * goarch.PtrSize)
///判断是否溢出
overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize
//修改后,newcap变成了842
newcap = int(capmem / goarch.PtrSize)

也就是说主要是内存对齐导致容量与预期的有所差异

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值