关于go:go源码分析切片

3次阅读

共计 2009 个字符,预计需要花费 6 分钟才能阅读完成。

slice

一、构造组成

切片实质上是一个构造体 slice,他的属性由上面三局部组成:

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

举个例子

咱们都晓得,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 的值。

上面是 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 的数据,如下图

下面相干代码基于 go1.18

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

正文完
 0