声明但不初始化
这个只是声明了 slice 的类型,并没有执行初始化操作,sli 是一个空指针 nil,没有进行内存分配
使用make进行初始化
make 初始化分为两种形式
第一种:
这个会将切片的长度和容量同时设置为10;容量很好理解,即这个切片可以容纳多少元素,而长度为该切片实际有多少元素,这里make会为我们设置10个int类型下的零值(即为0)
第二种:
这个会讲切片的容量设置为10,长度设置为8,8个元素均为int类型的零值,这里需要注意长度和容量的关系需满足 len < cap,访问元素的时候也只能访问已有的元素,比如sli[7],但是你如果访问sli[8]和sli[9],就会发生越界,导致panic
初始化并赋值
slice 可以直接完成初始化和赋值操作
这里会将切片的长度和容量都设置为4,且每个元素的值都已经设置好
append可以对切片进行追加操作,可以追加元素或切片
假设我们声明了一个int类型的切片arr,追加元素和切片的操作如下所示:
append操作很灵活,可以有很多操作,而且append和slice的扩容机制有关
copy是Go语言的内置函数,用于将一个切片(源切片 src)的元素按值复制到另一个切片(目标切片 dst)
copy 操作总是涉及值复制。操作完成后,dst 和 src 不会共享任何底层数组。它们是完全独立的数据副本。
子切片操作是指从一个现有切片或数组中创建一个新的切片。这是切片设计中最容易引起混淆但又最强大的特性。
语法: newSlice := oldSlice[low:high] 或 newSlice := oldSlice[low:high:max]
底层关系: 子切片和原始切片共享同一个底层数组。 它们只是引用了底层数组的不同部分
| 元素 | 描述 |
|---|---|
| low | 新切片的首个元素的索引(基于原切片/数组),newSlice[0] 对应 oldSlice[low] |
| high | 新切片的长度界限(不包含 high 索引的元素)。新切片的长度为 high - low |
| max (容量上限) | 可选的第三个索引,用于设定新切片的容量界限。新切片的容量为 max - low |
len(s) : 获取切片的长度
cap(s) : 获取切片的容量
切片不是数据容器本身,而是对底层数组的引用和描述。每个切片在内存中都由一个包含三个字段的结构体表示,通常称为“切片头部”(Slice Header)
| 字段 | 含义 | 作用 |
|---|---|---|
| array | 底层数组指针 | 指向切片所引用数据的起始内存地址 |
| len | 切片的长度 | 切片当前包含的元素数量,即 len(s) |
| cap | 切片的容量 | 从切片起始位置到其底层数组末尾的元素数量,即 cap(s) |
核心关系: 多个切片可以共享同一个 Array 指针,但它们可以有不同的 Len 和 Cap,这就是子切片操作的原理
切片在 Go 语言中是引用类型的一种体现(尽管切片头部是按值传递的结构体,但其内部的指针使其行为类似于引用)
Array 指针指向了同一个底层数组。因此,在函数内部通过这个切片副本修改元素时(例如 s[i] = value),实际上修改的是底层共享的数据,会影响到原始切片例外: 如果在函数内部对切片执行 append 且触发了扩容,那么函数内部的切片将指向一个新的底层数组,与外部的原始切片解除关联,后续修改将不再影响外部切片
当使用 append 函数向切片追加元素,而切片的当前容量(Cap)不足时,就会触发扩容。扩容机制旨在平衡内存效率和性能,其步骤如下:
len(s) + 待追加元素数量 > cap(s) 时,需要扩容len(s) + 待追加数量),计算出一个更理想的新容量
newCap = oldCap * 2)newCap = oldCap * 1.25),只到容量满足要求append 返回一个新的切片头部,其 Array 指针指向新数组,Len 更新为追加后的长度,Cap 更新为新容量正是因为这个数据复制过程,频繁的扩容操作会导致性能下降。因此,推荐使用 make([]T, 0, initialCap) 来预分配切片容量
当通过子切片操作(Reslice) (s[low:high]) 从一个大容量的切片中截取出一小段数据时,新生成的子切片虽然长度很小,但它与原切片共享同一个底层数组。
内存驻留: 只要这个小切片(即便是只包含少数元素的变量)仍然存活并被引用,那么它所引用的整个巨大的底层数组就无法被 Go 的垃圾回收器(GC)回收。
后果: 这会导致内存中驻留了大量不再需要的旧数据,造成内存效率低下,在长时间运行的服务器或需要处理大文件的场景中,可能引发内存泄漏或不必要的内存压力。
推荐做法:使用 copy 彻底断开底层关联
为了解决这个潜在的内存驻留问题,我们推荐使用 copy 函数来创建数据的完全独立副本。
make 函数为新切片分配一个大小刚好够用的新底层数组。copy 函数将原切片中需要保留的数据按值复制到这个新切片中。