[Go] slice切片详解

切片详解

切片的实现

Go 中的切片本质上是一个结构体,包含以下三个部分:

  • 指向底层数组的指针array):切片指向一个底层数组,数组中存储着切片的数据。
  • 切片的长度len):切片中当前元素的个数。
  • 切片的容量cap):底层数组的总容量,即底层数组能够存储的元素个数。
type slice struct {
   
   
    array unsafe.Pointer
    len   int
    cap   int
}

切片的扩容

切片在添加元素(如append操作)时,如果切片的长度大于容量,那么会重新分配一个新的容量更大的底层数组来存储新切片

切片在初始化的时候长度等于容量,当向切片添加元素时切片的长度就会大于容量,此时就会为其分配一个新的底层数组

func main() {
   
   
	t := []int{
   
   1, 2, 3}
	fmt.Println(cap(t))
	fmt.Printf("%p\n", t)
	t = append(t, 1)
	fmt.Println(cap(t))
	fmt.Printf("%p\n", t)

	//3
	//0xc0000120a8
	//6
	//0xc00000c360
}

在该示例中,初始化切片的长度为3,容量也为3,切片的底层数组的地址为0xc0000120a8,当向其添加一个元素后,切片的容量为6,并指向了一个新的地址0xc00000c360

Tips:使用 fmt.Printf("%p\n", t) 获得的是切片指向的底层数组的地址与 fmt.Printf("%p\n", unsafe.Pointer(&t[0])) 的值相同,使用 fmt.Printf("%p\n", &t) 获得的是切片变量本身的地址

我们可以使用make()创建一个容量大于长度的切片,这样在向切片添加元素时,长度就不会超过容量,就不会分配新的数组

func main() {
   
   
	t := make([]int, 3, 5)
	fmt.Println(len(t))
	fmt.Println(cap(t))
	fmt.Printf("%p\n", t)

	t = append(t, 1)
	fmt.Println(cap(t))
	fmt.Printf("%p\n", t)

	//3
	//5
	//0xc00000c360
	//5
	//0xc00000c360
}

在该示例中,我们使用make([]int, 3, 5)创建了一个长度为3,容量为5的切片,随后我们向其添加了一个元素,长度为4没有超过容量,此时底层数组的地址与添加元素前的地址一致,并没有分配新的数组。

切片扩容的实现

在第一个示例中,当我们向一个容量为3的切片添加一个元素后,新分配的切片容量为6,这个是如何得出的呢?

切片扩容的代码为 growslice

func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice {
   
   
    oldLen := newLen - num
    if raceenabled {
   
   
       callerpc := getcallerpc()
       racereadrangepc(oldPtr, uintptr(oldLen*int(et.Size_)), callerpc, abi.FuncPCABIInternal(growslice))
    }
    if msanenabled {
   
   
       msanread(oldPtr, uintptr(oldLen*int(et.Size_

你可能感兴趣的:([Go] slice切片详解)