Golang中Slice 底层机制的实现

 更新时间:2026年03月04日 09:16:23   作者:Das1_  
本文主要介绍了Golang中Slice 底层机制的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

1. 核心数据结构

切片本身并不存储数据,而是底层数组的一个“视图”或描述符。其底层结构(reflect.SliceHeader)仅占用 3 个机器字长(64位系统为24字节):

  • Data(指针) :指向底层数组中切片起点的真实内存地址。
  • Len(长度) :切片当前可访问的有效元素个数。
  • Cap(容量) :底层数组从 Data 指针位置开始,到其物理空间末尾的总容量。

2. Len 与 Cap 的设计哲学

相比较于C语言的数组,为什么golang中还有len和cap的说法,既然底层已经占据了cap的内存,len不是多此一举吗?

分离 Len 与 Cap 的核心目的是兼顾性能安全

  • Cap (性能缓冲) :用于向操作系统“批发”内存。避免频繁的 append 操作每次都触发高昂的系统调用和内存分配(平摊开销)。
  • Len (语义与安全) :划定有效数据的逻辑边界。拦截对已分配但未初始化(或已废弃)内存区域的访问,防止脏数据泄漏。

3. 切片表达式与底层坐标系

在表达式 arr[low : high : max] 中,三个参数均代表相对于原数组的物理索引,而非直接的长度或容量数值。

  • 指针偏移:新切片的 Data 指针指向 arr[low]low 之前的元素从新切片的视野中被永久剥离。
  • 长度计算Len = high - low
  • 容量计算Cap = max - lowmax 绝对不能超过原底层数组的最大容量)

应用场景:完整切片表达式主要用于内存隔离,限制切片的 Cap,强制其在未来 append 时触发扩容,从而避免意外覆盖底层数组中的后续数据。

对于max的理解

max实际上是一个相对于原数组的索引,表示slice在原数组上的最大右边界索引,而不是直接指定新切片的容量数字。

arr[1:4:7] 时,底层其实是在说:“把索引 1 作为起点,索引 4 作为当前视口的终点,并且把索引 7 作为底层物理空间的绝对禁区(不能达到或越过索引 7)

var arr = [...]int{0, 1, 2, 3, 4, 5, 6}  
slice1 := arr[1:4:5] // 左闭右开区间,最大容量为 5
// slice1 => [1 2 3]  
fmt.Println(slice1)  
slice2 := arr[1:4:7]   
// high <= max <= cap 虽然 1 + 7 = 8 > 7=cap,但是这里不会报错  
// slice2 => [1 2 3]  
fmt.Println(slice2)

4. 扩容机制 (Append)

切片长度增加时存在两种截然不同的物理行为:

  • 假扩容(在 Cap 范围内) :底层不分配任何新内存。切片仅仅是向后推移其右边界(Len 增加),占用底层数组中预留的 Cap 空间,原地写入新数据。
  • 真扩容(超出 Cap) :触发内存重分配。Runtime 申请一块全新、更大的连续内存,将旧底层数组的数据全量拷贝至新内存,追加新元素,最后将切片的 Data 指针修改为指向新内存(旧数组无引用后被 GC 收回)。

当真扩容发生时,slice追加的数组超出了原数组的cap,go内部创建一个新的数组,此时该slice的引用地址不再是arr

var arr = [...]int{1, 2, 3, 4}  
fmt.Println(arr) //[1 2 3 4]  
slice1 := arr[:]  
fmt.Println(slice1) //[1 2 3 4]  
slice1 = append(slice1, []int{5, 6, 7}...)  
fmt.Println(slice1) //[1 2 3 4 5 6 7]  
slice1 = append(slice1, 8)  
slice1[0] = 888  
fmt.Println(slice1) // [888 2 3 4 5 6 7 8]  
fmt.Println(arr)    // [1 2 3 4]

而假扩容时,则仍是在原数组上进行追加而已

var arr = [6]int{1,2,3,4}
fmt.Println(arr) //[1,2,3,4,0,0]
slice := arr[:4]
fmt.Println(slice) //[1,2,3,4]
slice = append(slice,5)
fmt.Println(arr) //[1,2,3,4,5,0]
fmt.Println(slice) //[1,2,3,4,5]

5. 参数传递机制:为何能修改原数组?

Go 语言所有参数传递均为严格的值传递(拷贝)

数组传参:拷贝整个数组的全部元素(全量复印)。在函数内修改副本,原数组毫无影响。

切片传参:仅拷贝 SliceHeader(指针、Len、Cap 的拷贝)。

  • 表现为“引用”的根本原因:复印件中的 Data 指针与原件指向同一块底层数组内存。因此修改元素会穿透到原数组。
  • 核心陷阱:在函数内对切片执行 append,虽然可能修改底层数据,但无法改变外部切片的 LenCap(因为修改的是副本里的字段)。若触发了“真扩容”,内部切片将指向新数组,与外部彻底脱轨。

简单来说:- 因为切片是个引用类型,所以它作为参数传递给函数,函数操作的实质是底层数组

func main() {
	var slice = make([]int,3,5) //len=3,cap=5
	fmt.Println(slice)  //[0,0,0]
	slice2:=slice[:5]  //slice实现了对slice的扩容,切片长度变为5
	fmt.Println(slice2) //[0,0,0,0,0]
	slice[0] = 999  //这里slice和slice的index=0位置都是999 因为他们引用的底层数组的index=0位置都是999
	fmt.Println(slice)
	fmt.Println(slice2)
	AddOne(slice) //[8888,0,0]
	fmt.Println(slice) //[8888,0,0]
	fmt.Println(slice2) //[8888,0,0,0]
}
func AddOne(s []int){
	s[0] = 8888
	fmt.Println(s)
}

6. 对于slice 的 slice

在 Go 语言中,每一次切片操作都会建立一个 “全新的相对坐标系” 。当你对一个 slice 再次进行切片时,所有的索引(lowhighmax)都是基于当前这个 slice 的起点(索引 0) 来计算的,它根本不关心最底层的数组是从哪里开始的。

var arr = [...]int{0, 1, 2, 3, 4, 5, 6}  
slice1 := arr[1:4:5] // 左闭右开区间,最大容量为 5
// slice1 => [1 2 3]  
slice3 := slice1[1:3:4]
fmt.Println(slice3)  
// slice3 => [2 3]  这里的从slice[1]开始
slice4 := slice1[1:4]  
// slice4 => [2 3 4]  
fmt.Println(slice4)

到此这篇关于Golang中Slice 底层机制的实现的文章就介绍到这了,更多相关Golang Slice底层机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:

相关文章

  • go语言的panic和recover函数用法实例

    go语言的panic和recover函数用法实例

    今天小编就为大家分享一篇关于go语言的panic和recover函数用法实例,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-04-04
  • golang 原生database\sql 的重连机制实现

    golang 原生database\sql 的重连机制实现

    本文主要介绍了golang 原生database\sql 的重连机制实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-02-02
  • 一篇文章带你搞懂Go语言标准库Time

    一篇文章带你搞懂Go语言标准库Time

    在我们开发的过程中,每个项目都需要时间这一类的函数,此时对time这个包的研究深度就显得尤为重要,这篇文章主要给大家介绍了关于如何通过一篇文章带你搞懂Go语言标准库Time的相关资料,需要的朋友可以参考下
    2022-10-10
  • Go语言实战学习之流程控制详解

    Go语言实战学习之流程控制详解

    这篇文章主要为大家详细介绍了Go语言中的流程控制,文中的示例代码讲解详细,对我们学习Go语言有一定的帮助 ,需要的朋友可以参考下
    2022-08-08
  • Go语言基础学习之指针详解

    Go语言基础学习之指针详解

    Go 语言中指针是很容易学习的,Go 语言中使用指针可以更简单的执行一些任务。所以本文就来和大家聊聊Go语言中指针的定义与使用,需要的可以参考一下
    2022-12-12
  • go nil处理如何正确返回nil的error

    go nil处理如何正确返回nil的error

    这篇文章主要为大家介绍了go中的nil处理,如何正确返回nil的error实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • 解决Golang并发工具Singleflight的问题

    解决Golang并发工具Singleflight的问题

    前段时间在一个项目里使用到了分布式锁进行共享资源的访问限制,后来了解到Golang里还能够使用singleflight对共享资源的访问做限制,于是利用空余时间了解,将知识沉淀下来,并做分享
    2022-05-05
  • Go项目开发中如何读取应用配置详解

    Go项目开发中如何读取应用配置详解

    本文主要介绍了Go项目开发中如何读取应用配置详解,Go生态中有很多包可以加载并解析配置,最受欢迎的是Viper包,下面就来详细的介绍一下
    2024-05-05
  • GoLang中sql.Exec()报错解决办法

    GoLang中sql.Exec()报错解决办法

    这篇文章主要给大家介绍了关于GoLang中sql.Exec()报错的解决办法,文中通过代码介绍的非常详细,对大家的学习或者工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2024-01-01
  • Go语言MD5加密用法实例

    Go语言MD5加密用法实例

    这篇文章主要介绍了Go语言MD5加密用法,实例分析了Go语言MD5加密的使用技巧,需要的朋友可以参考下
    2015-03-03

最新评论