go源码分析——切片

slice

一、结构组成

切片本质上是一个结构体slice,他的属性由下面三部分组成:

  • array:元素存哪里
  • len:已经存了多少
  • cap:容量是多少
type slice struct {
  array unsafe.Pointer //数组的指针
  len   int //切片长度
  cap   int //切片容量
}

go源码分析——切片_第1张图片

举个例子

我们都知道,go语言的方法传参数都是值传递。

因为切片类型是一个结构体,所以当传到change方法的时候,形参arr是arrOri一份副本。但是因为属性array是一个指针,所以改变arr的值,也会影响到arrOri

func main() {
  arrOri := []string{"0", "1", "2"}
  change(arrOri)
  fmt.Println(arrOri[0])
}
func change(arr []string) {
  if len(arr) > 0 {
    arr[0] = "test0"
  }
}
//output:test0

二、append发生了什么?

通过上面的例子我们知道,如果某个方法改变的切片某个位置的值,也会影响到原切片。

下面我们再看一下这个例子,为什么最后输出是0,而不是test0呢?接下来我们继续来聊聊扩容

func main() {
  arrOri := []string{"0", "1", "2","3"}
  change2(arrOri)
  fmt.Println(arrOri[0])
}
func change2(arr []string) {
  arr = append(arr, "4")
  if len(arr) > 0 {
    arr[0] = "test0"
  }
}
//output:0

我们最开始声明切片arrOri的时候给了4个元素,那切片的初始容量为4,array如下图的上面的数组。

但当我们添加一个元素的时候,原先的数组的长度已经不够我们增加元素。

所以需要声明一个新的更长数组,把老数组的数据复制到新的数组中去,然后追加我们需要添加的元素,最后把切片的属性array的指针指向新的数组。

所以arrOri的array属性指向的是老数组,而arr的array属性指向的是新的数组,改变arr的值不会影响到arrOri的值。

go源码分析——切片_第2张图片
下面是append是的源码解析

// growslice 切片容量增长函数
//  扩容概括:1.需要的cap大于2倍旧cap -> 增长到新的cap
//      2.旧cap小于256 -> 双倍增长
//      3.旧cap大于256 -> 1.25倍+192 的速度线性扩容,直到新切片的cap大于需要的cap
//  @param et 切片数据的类型
//  @param old 老的切片
//  @param cap 需要增长到的容量
//  @return slice 返回新的切片
func growslice(et *_type, old slice, cap int) slice {
  //切片的数据类型的大小为0(比如struct{})
  //  则数组可以不用具体的值,只需要改变len 和 cap的值
  if et.size == 0 {
    return slice{unsafe.Pointer(&zerobase), old.len, cap}
  }

  newcap := old.cap
  doublecap := newcap + newcap 
  if cap > doublecap { 
    //如果【新的切片需要的cap】>【老切片cap】的两倍,则扩容到新的切片需要的cap
    newcap = cap
    
  } else {
    const threshold = 256
    if old.cap < threshold { 
      //如果原数组长度 < 256,容量翻倍
      newcap = doublecap
    } else {
      //如果原数组长度 >= 256,
      //容量以【1.25*newcap + 192】线性速度速度慢慢扩容,直到newcap大于需要的cap              
      for 0 < newcap && newcap < cap {
        newcap += (newcap + 3*threshold) / 4
      }
      if newcap <= 0 {      //原先容量为0,则容量为参数cap
        newcap = cap
      }
    }
  }

  var overflow bool
  var lenmem, newlenmem, capmem uintptr
  
  //分配对象
  lenmem = uintptr(old.len) * et.size 
  p = mallocgc(capmem, et, true)


  //把旧数据old.array复制到新数组p中
  memmove(p, old.array, lenmem)
  
  return slice{p, old.len, newcap}
}

三、截断发生了什么

func main() {
  a := []int{1, 2, 3, 4, 5, 6}
  b := a[2:5]
  a[2] = 999
  fmt.Println(b)
}
//output:[999 4 5]

当我们进行切片截断的时候,新切片b的array属性指针和a的array指针指向同一个数组(如下图),只是len和cap属性值不同,所以改变a的值会影响到b的数据,如下图

go源码分析——切片_第3张图片

上面相关代码基于go1.18

https://github.com/golang/go/...

你可能感兴趣的:(go后端)