【go语言基础】slice

1. slice是什么

slice是一个大小可以动态扩展的类数组的结构。它的底层通过指针引用着一个大小确定的数据,这个数组作数据存储。

当大小够用的时候引用的数组大小保持不变。当引用的数组大小不够用时,slice会申请新大小合适的数组空间,然后将数据复制到新的数组,最后slice的引用新的数组的地址。

slice的原型结构

go 复制代码
type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}
  • 长度(len) :切片当前包含的元素个数。
  • 容量(cap) :从切片的起始位置到底层数组的末尾的元素个数。

在开发中几乎都是使用slice,很少使用数组。

2. slice在解决什么问题

  • 数组在编译阶段就需要给定大小,而有些数据只有在运行时才可以知道大小,会导致:
    • 所以在编程的时候如果数组大小不够,需要重新申请内存,然后复制,消耗性能。
    • 编译时申请太大的数组可能会浪费内存。
  • 不知道数组实际存放了多少个数据,如果想知道,需要对数组多二封装,增加工作量。
  • 数组内存共享困难,只能靠复制,如果不复制就丢失数组的特性。因为go的数组内存共享只有slice,所以这里拿C语言举例:
arduino 复制代码
#include <stdio.h>

void printArray(int* arr, int len) {
    for (int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }
}

int main() {
    int data[3] = {1, 2, 3};
    printArray(++data, 2); // 只能传指针和长度
    return 0;
}

这个代码实现了内存共享,但丢失数组的大小,需要另外一个参数做说明长度的补充,所以比较繁琐。

go 复制代码
package main

import "fmt"

func main() {
    arr := []int{1, 2, 3, 4, 5}
    s1 := arr[1:4] // s1 = [2 3 4]
    s2 := arr[2:]  // s2 = [3 4 5]

    fmt.Println("s1:", s1)
    fmt.Println("s2:", s2)

    s1[0] = 99 // 修改 s1 同时影响 arr 和 s2
    fmt.Println("arr:", arr)
}

内存得到了共享,没有丢失长度,而且还有容量

2.1.1. 问题总结

问题 C语言数组(或 Go数组) Go切片
不确定大小 编译时固定 运行时动态增长
扩容麻烦 需手动申请 + 复制 append自动处理
长度管理 需额外变量维护 len(slice)内建支持
共享困难 只能手动传指针、复制数据 多个切片可共享底层数组

3. slice怎么解决

  • 容量可以动态扩容
  • 存放数据的个数可知
  • 可以与数组或slice共享内存,不切不丢失长度和容量

4. slice的特性

特性 描述说明
动态扩容 底层数组不足时,自动分配新数组并复制旧数据
共享内存 多个切片可共享底层数组(类似 C 中的指针),节省内存且支持原位修改
内建长度与容量 内建 lencap,不需要手动传入长度
轻量结构 仅 3 个字段,传参/赋值成本低
值类型 + 引用语义 本质是值类型,但包含指向共享底层数组的指针,因此具有引用语义
简化编程 比 C 或 Go 原生数组更灵活,适合日常开发场景

4.1. 轻量

在go里将一个数组传入函数参数,则会复制一份数组。在c语言下默认是引用,但丢失了数组的容量。slice是一个简单的数据结构(值类型),作为参数也会复制一份slice,但slice引用的数组不会被分支,所以成本低。在x86_64中,slice真用的内存大小为24字节。

虽然官方文档说slice是引用类型,但是它更符合值类型的特征

4.2. 容量可以动态调整

当大小够用的时候引用的数组大小保持不变。当引用的数组大小不够用时,slice会申请新大小合适的数组空间,然后将数据复制到新的数组,最后slice的引用新的数组的地址。

4.3. 保存数据的个数和容量可以随时获取

4.3.1. 1. append 配合使用,扩容自动处理

go 复制代码
s := []int{1, 2}
s = append(s, 3, 4)
  • 若容量足够,append 就在原数组上操作;
  • 若容量不足,会创建新数组,复制数据,然后返回新的切片;
  • 返回的是新的切片(重要!)

4.4. 2. 切片间共享的数据是可变的,但结构是独立的

go 复制代码
a := []int{1, 2, 3}
b := a[:2]   // b 与 a 共享数据
b[0] = 100   // 修改 b 会影响 a

切片结构体(Data, Len, Cap)是复制的,但底层数组是共享的。


4.5. 3. slice 的 cap 和 len 是分开的

go 复制代码
a := make([]int, 2, 5)
fmt.Println(len(a), cap(a)) // 2 5
  • len 控制能访问的元素个数
  • cap 控制 append 时可以继续使用多少容量

这在构建 buffer 时非常有用。


4.6. 4. 支持 nil 切片和空切片

go 复制代码
var a []int        // nil 切片,a == nil
b := make([]int, 0) // 空切片,b != nil
  • len == 0
  • nil 切片常用于表示"没有值"
  • 空切片常用于表示"值是空的集合"

4.7. 5. 切片是安全的内存视图

即使多个切片共享内存,但你无法通过切片访问超出 len 范围的数据(否则 panic),这提高了安全性。


4.8. 6. 切片可以截取切片

你可以对一个切片继续切片(称为"re-slice"):

go 复制代码
s := []int{1, 2, 3, 4}
s2 := s[1:3]      // [2 3]
s3 := s2[1:]      // [3]

4.9. 7. slice 与 copy 函数配合做深复制

go 复制代码
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)

适用于避免共享底层数据的场景。


4.10. 8. 可以与 range 安全配合迭代

css 复制代码
for i, v := range slice {
    // i 是索引,v 是值拷贝
}

这是切片的标准使用模式之一。


4.11. 🔚 总结:slice 的附加特性(补充点)

特性名称 描述
append 自动扩容 不够就重新分配
re-slice 支持 切片还能继续切
copy 支持 可用于深拷贝
range 安全迭代 配合 range 可读性高
nil/空区分 nil 表示无,空表示空集合
cap 控制预分配 可优化性能,减少扩容次数
安全边界限制 超过 len 会 panic,防止越界
可以表示动态数组 在 Go 中承担动态数组的角色
相关推荐
yuadsl30109 分钟前
heyicache--性能极限版的freecache
go
Code季风12 分钟前
从超卖到数据一致:分布式锁的最佳实践与演进历程
分布式·微服务·go
程序员爱钓鱼1 小时前
Go语言实战案例:使用channel实现生产者消费者模型
后端·go·trae
程序员爱钓鱼1 小时前
Go语言实战案例:使用select监听多个channel
后端·go·trae
五岁小孩吖18 小时前
Go 单元测试 testing 包详解(testing.T、M、B、PB)
go
楽码20 小时前
一文看懂GO新版映射实现SwissTable
后端·算法·go
Code季风1 天前
什么是微服务分布式配置中心?
分布式·微服务·go
Code季风1 天前
微服务配置治理实战:Gin 与 gRPC 服务集成 Nacos 全解析
分布式·微服务·go