当前位置: 首页 > news >正文

Golang是如何实现动态数组功能的?Slice切片原理解析

Hi 亲爱的朋友们,我是 k 哥。今天,咱们聊一聊Golang 切片。

当我们需要使用数组,但是又不能提前定义数组大小时,可以使用golang的动态数组结构,slice切片。在 Go 语言的众多特性里,slice 是我们经常用到的数据结构。但您有没有想过,它在背后是怎么工作的呢?接下来,咱们就一起仔仔细细地研究研究 slice 的底层到底是咋回事。比如它的底层数据结构是咋样的,又是怎么和数组配合实现动态数组功能的。把这些弄明白了,咱们写代码不光能更高效,还能躲开不少容易出错的地方。

原理

数据结构

我们每定义一个slice变量,golang底层都会构建一个slice结构的对象。slice结构体由3个成员变量构成:

  • array表示数组指针,数组用于存储数据。

  • len表示切片长度,也就是数组index从0到len-1已存储数据。

  • cap表示切片容量,当切片长度超过最大容量时,需要扩容申请更大长度的数组。

type slice struct {array unsafe.Pointer // 数组指针len   int // 切片长度cap   int // 切片容量
}

切片扩容

当我们往切片中append时,如果新添加数据会导致切片的len>cap,则会触发扩容。申请容量更大的新数组,并将旧数组数据复制到新数组。

当切片扩容时,新申请的数组长度要满足3个需求:

  1. 数组要能承载本次数据append进去,这是基本要求。

  2. 除了1中的基本要求,可以额外多申请一部分空间,防止后续append频繁扩容,引起性能问题。

  3. 额外申请的空间不能过大,防止内存浪费。

为了满足上述的3个要求,golang底层的扩容策略是如果需要的容量比旧容量大很多,则不申请额外的空间;如果需要的容量比旧容量并没有大很多,则可以多申请一些额外的内存空间。具体策略如下:

  1. 如果本次append之后,需要的容量大于旧切片容量*2,则新切片容量等于需要的容量。

  2. 如果旧切片容量<256,则新切片容量为旧切片容量*2。

  3. 否则,以公式newcap += (newcap + 3*threshold) / 4迭代,直到newcap大于需要的容量为止,将newcap作为新切片容量。

// growslice handles slice growth during append.
// It is passed the slice element type, the old slice, and the desired new minimum capacity,
// and it returns a new slice with at least that capacity, with the old data
// copied into it.
// The new slice's length is set to the old slice's length,
// NOT to the new requested capacity.
// This is for codegen convenience. The old slice's length is used immediately
// to calculate where to write new values during an append.
// 参数cap表示本次append之后需要的切片容量
func growslice(et *_type, old slice, cap int) slice {// 扩容策略,决定扩容后切片容量newcap,也就是需要申请的新数组长度。newcap := old.capdoublecap := newcap + newcapif cap > doublecap {newcap = cap} else {const threshold = 256if old.cap < threshold {newcap = doublecap} else {// Check 0 < newcap to detect overflow// and prevent an infinite loop.for 0 < newcap && newcap < cap {// Transition from growing 2x for small slices// to growing 1.25x for large slices. This formula// gives a smooth-ish transition between the two.newcap += (newcap + 3*threshold) / 4}// Set newcap to the requested cap when// the newcap calculation overflowed.if newcap <= 0 {newcap = cap}}}// 计算新切片的容量、长度var overflow boolvar lenmem, newlenmem, capmem uintptrlenmem = uintptr(old.len) * et.sizenewlenmem = uintptr(cap) * et.sizecapmem, overflow = math.MulUintptr(et.size, uintptr(newcap))capmem = roundupsize(capmem)newcap = int(capmem / et.size)// 数组内存申请var p unsafe.Pointerif et.ptrdata == 0 {p = mallocgc(capmem, nil, false)} else {p = mallocgc(capmem, et, true)}// 数据复制memmove(p, old.array, lenmem)// 构建新的切片返回return slice{p, old.len, newcap}
}

for 循环的坑

在for和for range循环中,对于循环迭代变量,它的作用域是整个循环。在循环时,会创建一个变量,每次迭代都会把值赋给同一个地址的变量。如果我们的代码有引用这个变量,可能会出现不符合预期的结果。

比如下面对for循环迭代变量i的使用,会输出不符合预期的结果。

func main() {var out []*intfor i := 0; i < 3; i++ {out = append(out, &i)}fmt.Println("Values:", *out[0], *out[1], *out[2]) // 输出 Values: 3 3 3fmt.Println("Addresses:", out[0], out[1], out[2]) // 输出 Addresses: 0x40e020 0x40e020 0x40e020
}

原因是在每次迭代中,我们将变量 i 的地址附加到 out 切片,但由于它是同一个变量,因此我们附加相同的地址,该地址最终包含分配给 i 的最后一个值。解决方案之一是将循环变量复制到新变量中:

 for i := 0; i < 3; i++ {
+   i := i // Copy i into a new variable.out = append(out, &i)}

改正之后输出符合预期的结果:

Values: 0 1 2
Addresses: 0x40e024 0x40e028 0x40e032

又比如下面for range循环,对迭代变量v的使用,也会输出不符合预期的结果。

package mainimport "fmt"type User struct {Name stringAge  int
}func main() {userMap := make(map[string]*User)users := []User{{Name: "a", Age: 22},{Name: "b", Age: 23},{Name: "c", Age: 21},}for _, v := range users {userMap[v.Name] = &v}for name, user := range userMap {fmt.Println(name, "=>", user.Age, ",Addresses:", &user) // 输出一下user的地址}
}

上面的代码输出不符合预期的结果,三个人的年龄和数据地址变成了相同值:

a => 21 ,Addresses: 0xc000012028
b => 21 ,Addresses: 0xc000012028
c => 21 ,Addresses: 0xc000012028

原因跟for循环一样,在循环时,创建了变量v,后续每次迭代都把值拷贝给变量v,导致循环结束后,拷贝的是最后一个,因此输出的age都是21。解决方案之一也是将循环变量复制到新变量中:

for _, v := range users {
+       temp := vuserMap[v.Name] = &temp
}

注意:为了防止循环迭代变量误用导致bug,在Go 1.22中,循环迭代变量的作用域,不再是循环作用域,而是迭代作用域,每次迭代都会申请一个新变量。Fixing For Loops in Go 1.22

实践经验

  1. 切片容量预分配。如果提前能预估切片容量,最好提前在make时就分配好容量,避免后续go底层的再次扩容,在一定程度上能提升代码执行效率。

  2. 注意对slice的循环,看是否需要将循环迭代变量赋值到一个临时变量使用,防止bug。

高频面试题

  1. 数组和切片的区别 (基本必问)

  2. 切片的扩容策略是怎样的?

  3. for range 的时候它的地址会发生变化么?for 循环遍历 slice 有什么问题?

  4. 拷贝大切片一定比小切片代价大吗?

对于浅拷贝,比如下面的切片赋值,拷贝大切片和小切片代价一样。原因是浅拷贝a和b共用底层数组,不需要重新申请数组空间,做数组数据迁移,而只需要将a的slice数据结构array、len、cap原样赋值给b的slice。

a:=[]int{1,2}
b:=a

对于深拷贝,比如调用copy函数拷贝,拷贝大切片比小切片代价大。原因是深拷贝a和b底层数组不共用,需要重新申请数组空间,并将a中数组元素复制到b,大切片的数据量大,因此数组申请和数据复制的代价也高一些。

a:=[]int{1,2}
b:=make([]int,0)
copy(b,a)

原文链接:<<Golang是如何实现动态数组功能的?Slice切片原理解析>>

相关文章:

Golang是如何实现动态数组功能的?Slice切片原理解析

Hi 亲爱的朋友们&#xff0c;我是 k 哥。今天&#xff0c;咱们聊一聊Golang 切片。 当我们需要使用数组&#xff0c;但是又不能提前定义数组大小时&#xff0c;可以使用golang的动态数组结构&#xff0c;slice切片。在 Go 语言的众多特性里&#xff0c;slice 是我们经常用到的数…...

SQL注入 报错注入+附加拓展知识,一篇文章带你轻松入门

第5关--------------------------------------------> 前端直接不会显示账号密码的打印&#xff1b;但是在接收前端的数据的那部分后端那里&#xff0c;会看前端传递过来的值是否正确&#xff0c;如果不正确&#xff0c;后端接收值那里就会当MySQL语句执行错误&#xff0c;…...

springboot项目里的包spring-boot-dependencies依赖介绍

springboot项目里的包’spring-boot-dependencies‘依赖 我们一般是在项目的pom dependencyManagement标签里引入spring-boot-dependencies&#xff0c;或者根spring-boot-starter-parent里也是继承了它,也正是因为继承了这个依赖&#xff0c;所以我们在写依赖时才不需要写版本…...

C# 下的限定符运算详解(全部,任意,包含)与示例

文章目录 1.限定符概述2. 全部限定符运算&#xff08;All&#xff09;3. 任意限定符运算&#xff08;Any&#xff09;4. 包含限定符运算&#xff08;Contains&#xff09;总结 当我们在C#编程中需要进行条件判断或集合操作时&#xff0c;限定符&#xff08;qualifiers&#xff…...

消息队列RabbitMQ部分知识

1.简述RabbitMQ的架构设计 RabbitMQ 是一个开源的消息代理&#xff0c;采用了高级消息队列协议&#xff08;AMQP&#xff09;&#xff0c;其架构设计主要包括以下几个关键组件和概念&#xff1a; 1.消息生产者&#xff08; Producer&#xff09;&#xff1a; 负责发送消息到…...

看门狗应用编程-I.MX6U嵌入式Linux C应用编程学习笔记基于正点原子阿尔法开发板

看门狗应用编程 看门狗应用编程介绍 看门狗定时器的基本概念 看门狗是一个可以在一定时间内被复位/重置的计数器 如果在规定时间内没有复位&#xff0c;看门狗计时器溢出会对CPU产生复位信号使系统重启 有些看门狗可以只产生中断信号而不会使系统复位 I.MX6UL/I.MX6ULL So…...

Bug 解决 | 本地项目上线后出现错误

目录 一、前言 二、原因分析 1、本地代码误发线上 2、环境差异 3、配置差异 4、资源路径差异 5、API 接口差异 6、用量差异 一、前言 大家好&#xff0c;我是小洪爱分享。在开发上线项目的过程中&#xff0c;我们经常会遇到一种让人头疼的情况。那就是开发好的项目功能…...

为什么我工作 10 年后转行当程序员?逆袭翻盘!

今天文章的主人公暂且称他为 A 君。不过 A 君有点特别&#xff0c;非科班&#xff0c;工作 10 年后才转行 iOS 程序员。今年 36 岁&#xff0c;目前在某行业头部企业任职前端负责人&#xff0c;管理 40 人的前端团队。 废话不多说&#xff0c;我们开始 A 君&#xff08;为了描…...

见证中国数据库的崛起:从追赶到引领的壮丽征程《四》

见证中国数据库的崛起&#xff1a;从追赶到引领的壮丽征程《四》 四、未来展望&#xff1a;中国数据库的机遇与挑战新技术带来的机遇全球化竞争的挑战数据安全与隐私保护的挑战人才培养的持续挑战 【纪录片】中国数据库前世今生 在数字化潮流席卷全球的今天&#xff0c;数据库作…...

OpenCV||超细节的基本操作

一、图像读取 retval cv2.imread(filename[, flags]) filename&#xff1a;需要读取的图片路径名&#xff0c;支持多种图片格式&#xff0c;如JPEG、PNG、TIFF等。flags&#xff1a;一个可选参数&#xff0c;指定加载图像的颜色类型。常用的值包括&#xff1a; cv2.IMGEAD_A…...

算法训练(leetcode)第三十八天 | 1143. 最长公共子序列、1035. 不相交的线、53. 最大子数组和、392. 判断子序列

刷题记录 *1143. 最长公共子序列1035. 不相交的线53. 最大子数组和392. 判断子序列 *1143. 最长公共子序列 leetcode题目地址 本题和718. 最长重复子数组相似&#xff0c;只是本题不要求连续&#xff0c;需要记录前面最长的子序列&#xff0c;在此基础上累计长度。 dp[i][j]…...

STM32——外部中断(EXTI)

目录 前言 一、外部中断基础知识 二、使用步骤 三、固件库实现 四、STM32CubeMX实现 总结 前言 外部中断&#xff08;External Interrupt&#xff0c;简称EXTI&#xff09;是微控制器用于响应外部事件的一种方式&#xff0c;当外部事件发生时&#xff08;如按键按下、传感器信号…...

MySQL多实例部署

1、软件包下载 //环境&#xff1a;一台rocky Linux虚拟机&#xff0c;并且做好的基本配置及时钟同步&#xff0c;使用Xshell连接 [rootmysql ~]# yum -y install tar lrzsz libncurses* libaio perl//将包文件拖进去 [rootmysql ~]# rz -E rz waiting to receive. [rootmysql…...

云开发喝酒小程序3.6全新漂亮UI猜拳喝酒小程序 【已去除流量主】

云开发喝酒小程序3.6全新漂亮UI猜拳喝酒小程序 已去除流量主。UI特别漂亮&#xff0c;实属精品代码。 【已测】云开发喝酒小程序3.6漂亮UI猜拳喝酒小程序 已去除流量主。 云开发&#xff08;serverless&#xff09;小程序无需服务器&#xff0c;注册一个小程序就可以直接上线…...

图论进阶之路-最短路(Floyd)

时间复杂度&#xff1a;O(n^3) 使用场景&#xff1a;当需要得知任意两个点的最短距离以及其路径时使用 准备&#xff1a;需要两个矩阵 一个记录最短距离&#xff08;D&#xff09; 一个记录最短路径的最后一个结点&#xff08;P&#xff09; 其核心在于不断的判断越过中间…...

安装sqllab靶机之后,练习关卡报403 forbidden

解决办法&#xff1a; 在nginx的conf文件中添加上访问index.php vim /usr/local/nginx/conf/nginx.conf 保存退出 再重启一下nginx&#xff0c;就完成了。 ./nginx -s reload...

微信VX多开 免扫码 登录 互斥体 可视化 Exui v1.1 易语言源码附成品软件

UI设计&#xff1a; 1. EXUI界面库20240204 调用的模块&#xff1a; 1. wow64_hook_3.02.ec&#xff08;压缩包内含&#xff09; 2. 精易模块[v11.1.0].ec&#xff08;自行下载&#xff09; 更新日志&#xff1a; v1.1 2024年7月25日13:28:43 { 1. 有人反馈 设置了V…...

JavaEE 从入门到精通(一) ~ Maven

晚上好&#xff0c;愿这深深的夜色给你带来安宁&#xff0c;让温馨的夜晚抚平你一天的疲惫&#xff0c;美好的梦想在这个寂静的夜晚悄悄成长。 目录 前言 1.1 概念 什么是 Maven&#xff1f; Maven 的核心概念 1.2 maven依赖坐标 1.3 maven仓库 1.4 maven安装 1.5 mave…...

滚珠丝杆与丝杆支撑座:稳定性与精度的双重保障

丝杆支撑座是连接滚珠丝杆与电机的轴承&#xff0c;采用优质的轴承能确保支撑座与滚珠丝杆之间的刚性平衡。那么&#xff0c;滚珠丝杆搭连接杆支撑座有哪些优缺点呢&#xff1f; 正常情况下&#xff0c;丝杆支撑座能够提供稳定的支撑力&#xff0c;确保滚珠丝杆在复杂工况下保持…...

实验5-11 空心的数字金字塔

本题要求实现一个函数&#xff0c;输出n行空心的数字金字塔。 函数接口定义&#xff1a; void hollowPyramid( int n );其中n是用户传入的参数&#xff0c;为[1, 9]的正整数。要求函数按照如样例所示的格式打印出n行空心的数字金字塔&#xff0c;请注意&#xff0c;最后一行的…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言&#xff1a;多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时&#xff0c;​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套&#xff1a;跨云网络构建数据…...

国防科技大学计算机基础课程笔记02信息编码

1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制&#xff0c;因此这个了16进制的数据既可以翻译成为这个机器码&#xff0c;也可以翻译成为这个国标码&#xff0c;所以这个时候很容易会出现这个歧义的情况&#xff1b; 因此&#xff0c;我们的这个国…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

第25节 Node.js 断言测试

Node.js的assert模块主要用于编写程序的单元测试时使用&#xff0c;通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试&#xff0c;通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案

随着新能源汽车的快速普及&#xff0c;充电桩作为核心配套设施&#xff0c;其安全性与可靠性备受关注。然而&#xff0c;在高温、高负荷运行环境下&#xff0c;充电桩的散热问题与消防安全隐患日益凸显&#xff0c;成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中&#xff0c;新增了一个本地验证码接口 /code&#xff0c;使用函数式路由&#xff08;RouterFunction&#xff09;和 Hutool 的 Circle…...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...