Go 中切片(Slice)的长度与容量

img

切片长度与容量在 Go 中很常见。切片长度是切片中可用元素的数量,而切片容量是从切片中第一个元素开始计算的底层数组中的元素数量。

Go 中的开发者经常混淆切片长度和容量,或者对它们不够了解。理解这两个概念对于高效处理切片的核心操作,比如切片的初始化、使用 append 添加元素、复制或切片操作等,至关重要。对这些概念的误解可能导致切片的不合理使用,甚至造成内存泄漏。

在 Go 中,切片是由数组支持的。这意味着切片的数据以连续的方式存储在数组数据结构中。切片还负责在底层数组已满时添加元素,或在几乎为空时缩减底层数组。

在内部,切片包含指向底层数组的指针,以及长度和容量。长度表示切片包含的元素数量,而容量表示底层数组中的元素数量,从切片中的第一个元素开始计算。让我们通过一些示例来更清楚地了解这些概念。首先,让我们使用给定的长度和容量初始化一个切片:

s := make([]int, 3, 6) // Three-length, six-capacity slice

第一个参数,表示长度,是必须的。但是,第二个参数表示容量是可选的。图1展示了此代码在内存中的结果。

img

Figure 1 — 一个长度为3、容量为6的切片

在这种情况下,make 创建了一个包含六个元素的数组(容量)。但由于长度设置为3,Go 只初始化了前三个元素。另外,因为切片是 []int 类型,所以前三个元素被初始化为 int 类型的零值:0。灰色元素已经分配但尚未使用。

如果我们打印这个切片,会得到长度范围内的元素 [0 0 0]。如果我们将 s[1] 设为1,切片的第二个元素会更新,但不会影响其长度或容量。图2说明了这一点。

img

图2 — 更新切片的第二个元素:s[1] = 1

然而,访问超出长度范围之外的元素是被禁止的,即使它在内存中已经分配。例如,s[4] = 0 会导致以下 panic:

panic: runtime error: index out of range [4] with length 3

我们如何使用切片剩余的空间呢?通过使用内置函数 append

s = append(s, 2)

这段代码向现有的 s 切片追加了一个新元素。它使用了第一个灰色元素(已分配但尚未使用)来存储元素2,正如图3所示。

img

图3 — 向 s 切片追加一个元素

切片的长度从3更新为4,因为现在切片包含了四个元素。现在,如果我们再添加三个元素以至于后台数组不够大,会发生什么?

s = append(s, 3, 4, 5)
fmt.Println(s)

如果我们运行这段代码,会看到切片能够满足我们的请求:

[0 1 0 2 3 4 5]

因为数组是一个固定大小的结构,在第4个元素之前,它能够存储新的元素。当我们想要插入第5个元素时,数组已经满了:Go 内部会创建另一个数组,将所有元素复制过去,然后再插入第5个元素。图4展示了这个过程。

img

图4 — 因为初始的后台数组已满,Go 创建了另一个数组并复制了所有元素。

现在切片引用了新的后台数组。之前的后台数组会怎样呢?如果它不再被引用,它最终会被垃圾收集器(GC)释放,如果它是在堆上分配的话(我们在错误#95 “不理解堆栈与堆的区别”中讨论了堆内存,并在错误#99 “不理解GC的工作原理”中讨论了GC的工作原理)。

对切片进行切片操作会发生什么?切片是对数组或切片进行的操作,提供了一个左闭右开的范围;第一个索引是包括的,而第二个索引是排除的。以下示例展示了影响,并在图5中显示了内存中的结果:

s1 := make([]int, 3, 6) // Three-length, six-capacity slice
s2 := s1[1:3] // Slicing from indices 1 to 3

img

图5 — 切片 s1 和 s2 引用相同的后台数组,但长度和容量不同

首先,s1 是一个长度为3、容量为6的切片。当通过对 s1 进行切片创建 s2 时,两个切片都引用同一个后台数组。但是,s2 从不同的索引开始,即索引1。因此,它的长度和容量(长度为2,容量为5)与 s1 不同。如果我们更新 s1[1]s2[0],则更改会作用于同一个数组,因此在两个切片中都是可见的,如图6所示。

img

图6 — 因为 s1 和 s2 共享同一个数组,更新共同的元素会使两个切片中的更改都可见

现在,如果我们向 s2 添加一个元素会发生什么?以下代码会同时改变 s1 吗?

s2 = append(s2, 2)

共享的后台数组被修改,但只有 s2 的长度发生了变化。图7展示了向 s2 添加元素的结果。

img

图7 — 向 s2 添加元素

s1 仍然是一个长度为3、容量为6的切片。因此,如果我们打印 s1s2,添加的元素只会在 s2 中可见:

s1=[0 1 0], s2=[1 0 2]

很重要理解这种行为,这样我们在使用 append 时就不会形成错误的假设。

注意: 在这些示例中,后台数组是内部的,不直接对 Go 开发者可见。唯一的例外是从现有数组切片创建切片。

还有最后一件事需要注意:如果我们不断向 s2 中添加元素,直到后台数组满为止,内存状态会是怎样的?让我们再添加三个元素,以便后台数组没有足够的容量:

s2 = append(s2, 3)
s2 = append(s2, 4) // At this stage, the backing is already full
s2 = append(s2, 5)

这段代码导致创建了另一个后台数组。图 8 展示了内存中的结果。

img

图 8 — 向 s2 添加元素直到后台数组已满

s1s2 现在引用两个不同的数组。由于 s1 仍然是一个三长度、六容量的切片,它仍然有一些可用缓冲区,因此它继续引用最初的数组。而且,新的后台数组是通过从 s2 的第一个索引复制初始数组而生成的。这就是为什么新数组从元素 1 开始,而不是 0。

结论

总结一下,切片长度 是切片中可用元素的数量,而 切片容量 是后台数组中的元素数量。向一个已满的切片(长度 == 容量)添加元素会导致创建一个新的后台数组,将之前数组中的所有元素复制到新数组中,并更新切片指向新数组。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/195287.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

天鹅湖国家旅游度假区 | 展柜OLED透明屏:创新展示提升互动体验

天鹅湖国家旅游度假区 | 展柜OLED透明屏 产品:一块55寸OLED透明屏嵌入玻璃安装 应用场景:用在天鹅湖国家旅游度假区——三门峡城市文化客厅展馆中的一个透明展示柜,用一块55寸OLED透明屏嵌入展示柜的玻璃,让观众即可以看到展柜里…

试写一算法将两个递增有序的带头结点的单链表合并为一个递增有序的带头结点的单链表。(利用原表结点空间)

试写一算法将两个递增有序的带头结点的单链表合并为一个递增有序的带头结点的单链表。 (利用原表结点空间) 比如现在要将下面两个链表合并,这里是要求利用原表空间 我们先创建一个辅助的链表L3,用p和q分别标记L1和L2的数据元素&…

​无人机石油管道巡检方案新亮点:灵活准确又高效

在当前石油工业的安全管理中,无人机技术逐渐成为一种不可或缺的工具。随着我国油气管道里程的持续增长,确保这些关键基础设施的安全运行变得越来越重要。传统的巡检方法已经无法满足现代油气行业的需求,而无人机石油管道巡检技术的应用提供了…

在CI/CD中使用submodule

背景信息 客户的submodule使用的是ssh协议拉取 前置操作 gitlab添加子模块 git clone ssh://gitkube.bdeet.top:2222/cicd/123.git cd 123/ ls -la git submodule add ssh://gitkube.bdeet.top:2222/approve/test-1.git git submodule add ssh://gitkube.bdeet.top:2222/mr…

oracle数据库备份2(expdp)

使用exp命令定时进行数据库备份的操作前面已经记录过: oralce数据库定时备份 下面记录下使用更加高效的expdp命令和impdp,这两个命令同样是用来做数据库备份和还原的,但速度更快,效率更高,缺点是只能用在服务器端进行…

企业为什么需要工作流自动化?

每一家企业都常常面临这些困扰:业务流程繁琐,工作人员每天花费大量的时间精力用于处理重复性的工作、审批流程复杂,开展工作前要经历层层审批,导致客户需求不能及时满足。想要打破僵局可以借助CRM系统的工作流自动化,什…

数学加速器:Python numpy.add函数全解读

更多Python学习内容:ipengtao.com Numpy是Python中用于科学计算的重要库之一,而numpy.add函数是其众多功能强大的函数之一。在本文中,将深入研究numpy.add函数的用法,通过丰富的示例代码,帮助大家更全面地理解和掌握这…

2023亚马逊云科技re:Invent,在开发者板块探究如何利用技术重塑业务

美国当地时间11月27日,一年一度的亚马逊云科技re:Invent大会在美国拉斯维加斯盛大开幕。这场全球云计算领域的前沿盛会,已连续12年成为引领行业的风向标。那么本次2023亚马逊云科技re:Invent大会又有哪些可玩、可看的新项目,下面就一起来瞧一…

亚马逊策略:通过影响者营销改造您的亚马逊商店

亚马逊卖家面临着从人群中脱颖而出的持续挑战 - 如果您是那些寻求变革性方法来接触目标受众、建立信任并将您的销售推向新高度的卖家之一,那么影响力营销就是您的答案。 您可能遇到过令人难以置信的成功故事,产品一夜之间流行起来,仅仅是因为…

基于oracle数据库的PLSQL编程以及存储过程的创建和使用

PL/SQL编程 declare begindbms_output.put_line(helloworld!);-- line表示换行 end;set serveroutput on; 开启打印输出 / 表示结束变量的声明与赋值 declarev_name varchar2(20) : 张三;v_sal number;v_addr varchar2(200); begin-- 直接赋值v_sal : 1111;-- 语句赋值selec…

Docker Swarm总结+Jenkins安装配置与集成(5/5)

博主介绍:Java领域优质创作者,博客之星城市赛道TOP20、专注于前端流行技术框架、Java后端技术领域、项目实战运维以及GIS地理信息领域。 🍅文末获取源码下载地址🍅 👇🏻 精彩专栏推荐订阅👇🏻…

antd vue a-select 下拉框位置偏移

问题 下拉框未固定 原因 select下拉框的定位是根据body定位 解决方法 在select 标签中添加: :getPopupContainer"(triggerNode) > (triggerNode.parentElement)" :getPopupContainer"(triggerNode) > (triggerNode.parentElement)"…

Vue3-Eslint配置代码风格

prettier风格配置 官网:https://prettier.io Eslint:代码纠错,关注于规范 prettier:专注于代码格式化的插件,让代码更加美观 两者各有所长,配合使用优化代码 生效前提: 1)禁用…

二十章多线程

概念 有很多工作是可以同时完成的,这种思想放在Java中被称为并发,并发完成每一件事被称为线程。 程序员可以在程序中执行多个线程,每一个线程完成一个功能//与其他线程并发执行,这种机制被称为多线程,并不算所有编程…

phpstudy安装redis

Redis 是一个开源的高性能键值存储数据库,广泛用于缓存、消息队列、会话管理和实时数据分析等应用场景。 使用 PHP Redis 扩展,你可以在 PHP 代码中使用一系列的函数来连接到 Redis 服务器,并执行各种操作,如设置和获取键值对、操…

CLIPTokenizer.from_pretrained本地加载

以"openai/clip-vit-large-patch14"为例,原代码为: self.tokenizer CLIPTokenizer.from_pretrained(“openai/clip-vit-large-patch14”) self.transformer CLIPTextModel.from_pretrained(“openai/clip-vit-large-patch14”) 但我连不到外…

java学习part15单例模式

107-面向对象(高级)-单例设计模式与main()的理解_哔哩哔哩_bilibili 1.单例 就是说在某些开发场景中,某个类只要有一个对象就足够使用了,不需要重复创建。 (理解:比如说是数据库对象,使用时创建一个可以处理所有的数…

【网络安全】-常见的网站攻击方式详解

文章目录 介绍1. SQL 注入攻击攻击原理攻击目的防范措施 2. 跨站脚本攻击(XSS)攻击原理攻击目的防范措施 3. CSRF 攻击攻击原理攻击目的防范措施 4. 文件上传漏洞攻击原理攻击目的防范措施 5. 点击劫持攻击原理攻击目的防范措施 结论 介绍 在数字时代&a…

第13周 预习、实验与作业:Java网络编程

目录 1 课前问题列表 1.编写一个网络程序,为了与其他网络程序通信,至少要知道对方的什么信息? 2.TCP与UDP协议有什么不同的呢?什么时候该选择哪种协议?HTTP使用的是TCP还是UDP?不重要的短信息传送之类的功能…

GaussDB数据库SQL系列-触发器

目录 一、前言 二、触发器概念 三、GaussDB数据库中的触发器 1、语法格式 2、创建步骤 3、注意事项 4、附:表和视图上支持的触发器种类 四、GaussDB数据库中的示例 示例一、在GaussDB数据库中创建一个触发器,以便在插入新记录时自动将记录的创建…