当前位置: 首页 > 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;最后一行的…...

Java 语言特性(面试系列2)

一、SQL 基础 1. 复杂查询 &#xff08;1&#xff09;连接查询&#xff08;JOIN&#xff09; 内连接&#xff08;INNER JOIN&#xff09;&#xff1a;返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中&#xff0c;可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行&#xff0c;可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令&#xff0c;并忽略错误 rm somefile…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的学习进度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正确的建议&…...

微信小程序 - 手机震动

一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注&#xff1a;文档 https://developers.weixin.qq…...

华硕a豆14 Air香氛版,美学与科技的馨香融合

在快节奏的现代生活中&#xff0c;我们渴望一个能激发创想、愉悦感官的工作与生活伙伴&#xff0c;它不仅是冰冷的科技工具&#xff0c;更能触动我们内心深处的细腻情感。正是在这样的期许下&#xff0c;华硕a豆14 Air香氛版翩然而至&#xff0c;它以一种前所未有的方式&#x…...

CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)

漏洞概览 漏洞名称&#xff1a;Apache Flink REST API 任意文件读取漏洞CVE编号&#xff1a;CVE-2020-17519CVSS评分&#xff1a;7.5影响版本&#xff1a;Apache Flink 1.11.0、1.11.1、1.11.2修复版本&#xff1a;≥ 1.11.3 或 ≥ 1.12.0漏洞类型&#xff1a;路径遍历&#x…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

使用LangGraph和LangSmith构建多智能体人工智能系统

现在&#xff0c;通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战&#xff0c;比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...

git: early EOF

macOS报错&#xff1a; Initialized empty Git repository in /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/.git/ remote: Enumerating objects: 2691797, done. remote: Counting objects: 100% (1760/1760), done. remote: Compressing objects: 100% (636/636…...