Slice底层

数组

几乎所有计算机语言,数组的实现都是相似的:一段连续的内存。GO语言也一样,GO语言的数组底层实现就是一段连续的内存空间。

由于内存连续,CPU很容易计算索引,所以可以快速迭代数组里所有的元素。

C语言里面的Array是指向数组第一个元素的指针,GO语言的Array不像C语言里面的Array,在GO语言里,Array是一个Struct,一个数组变量就是整个数组。所以GO语言里Array是值类型,所以数组在传递的时候,传递的是原数组的拷贝。

GO语言slice底层数据结构

GO语言的Slice底层其实就是一个Struct:

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

它的struct包含三个字段:1.指向数组的指针 2.它的长度 3.它的容量

所以说,一个slice其实是截取了某个数组的一部分而已。

由于是指针,所以它的改变会直接影响到它所指向的数组。

当然我说的是影响它所指向的数组,如果它所指向的数组被更换了,就影响更换之后的数组了。

请看这段代码:

package main

import (
	"fmt"
)

func main() {
	var s = [5]string{"1", "2", "3", "4", "5"}
	fmt.Printf("原数组s:\n %v\n", s)

	sli := s[0:5]
	newsli := append(sli, "t")
	sli[1] = "x"
	fmt.Printf("截取数组s后的sli:\n %v\n", sli)
	fmt.Printf("原数组s:\n %v\n", s)
	fmt.Printf("append sli后的newsli:\n %v\n", newsli)

	newsli[0] = "w"
	fmt.Printf("改变了newsli的第一个元素之后的newsli:\n %v\n", newsli)
	fmt.Printf("原数组:\n %v\n", s)
	fmt.Printf("sli:\n %v\n", sli)
}

输出结果:

原数组s:
 [1 2 3 4 5]
截取数组s后的sli:
 [1 x 3 4 5]
原数组s:
 [1 x 3 4 5]
append sli后的newsli:
 [1 2 3 4 5 t]
改变了newsli的第一个元素之后的newsli:
 [w 2 3 4 5 t]
原数组:
 [1 x 3 4 5]
sli:
 [1 x 3 4 5]

由此可见,我们得出以下几个结论:

  • 以数组为基础建立的slice,这个slice的改变(改变slice里某个索引的值 或者 append的方式在其末尾添加元素),会直接影响到底层数组(它底层指针所指向的数组)

  • append的返回值是一个新的slice,它也是截取了跟它第一个参数(就是那个需要append的slice)一样的底层数组,所以这个append的返回值的改变,一样也直接影响到底层数组,它和那个append的第一个参数一样,它们的改变都能够直接影响到底层数组。

  • 如果append之后超过了原数组的容量,那么得到的新slice的变化将不再影响到该底层数组,因为超过了原有底层数组的容量之后,它的底层数组就会发生改变。GO会开辟一块新的内存把原来的值拷贝过来。

扩容的原则是:

  • 如果切片的容量小于1024个元素,那么扩容的时候slice的cap就翻番,乘以2;一旦元素个数超过1024个元素,增长因子就变成1.25,即每次增加原来容量的四分之一。