【golang】数组和切片底层原理

数组类型的值(以下简称数组)的长度是固定的,而切片类型的值(以下简称切片)是可变长的。

数组的长度在声明它的时候就必须给定,并且之后不会再改变。可以说,数组的长度是其类型的一部分。比如,[1]string和[2]string就是两个不同的数组类型。

切片的类型字面量中只有元素的类型,而没有长度。切片的长度可以自动地随着其中元素数量给的增长而增长,但不会随着元素数量的减少而减小。

image.png

我们其实可以把切片看做是对数组的一层简单的封装,因为在每个切片的底层数据结构中,一定会包含一个数组。数组可以被叫做切片的底层数组,而切片也可以被看作是对数组的某个连续片段的引用。

也正因为如此,Go 语言的切片类型属于引用类型,同属引用类型的还有字典类型、通道类型、函数类型等;而 Go 语言的数组类型则属于值类型,同属值类型的有基础数据类型以及结构体类型。

注意,Go 语言里不存在像 Java 等编程语言中令人困惑的“传值或传引用”问题。在 Go 语言中,我们判断所谓的“传值”或者“传引用”只要看被传递的值的类型就好了。

如果传递的值是引用类型的,那么就是“传引用”。如果传递的值是值类型的,那么就是“传值”。从传递成本的角度讲,引用类型的值往往要比值类型的值低很多

我们在数组和切片之上都可以应用索引表达式,得到的都会是某个元素。我们在它们之上也都可以应用切片表达式,也都会得到一个新的切片。

我们通过调用内建函数len,得到数组和切片的长度。通过调用内建函数cap,我们可以得到它们的容量。

数组的容量永远等于其长度,都是不可变的。切片的容量却不是这样,并且它的变化是有规律可寻的。

怎样正确估算切片的长度和容量?

package mainimport "fmt"func main() {
// 示例 1。s1 := make([]int, 5)fmt.Printf("The length of s1: %d\n", len(s1))fmt.Printf("The capacity of s1: %d\n", cap(s1))fmt.Printf("The value of s1: %d\n", s1)s2 := make([]int, 5, 8)fmt.Printf("The length of s2: %d\n", len(s2))fmt.Printf("The capacity of s2: %d\n", cap(s2))fmt.Printf("The value of s2: %d\n", s2)
}

首先,我用内建函数make声明了一个[]int类型的变量s1。我传给make函数的第二个参数是5,从而指明了该切片的长度。我用几乎同样的方式声明了切片s2,只不过多传入了一个参数8以指明该切片的容量。

那切片s1和s2的容量都是多少?

答案:切片s1和s2的容量分别是5和8。

问题解析

s1的容量为什么是5呢?

因为我在声明s1的时候把它的长度设置成了5。当我们用make函数初始化切片时,如果不指明其容量,那么它就会和长度一致。如果在初始化时指明了容量,那么切片的实际容量也就是它了。这也正是s2的容量是8的原因。

上述内容提到过,可以把切片看做是对数组的一层简单的封装,因为在每个切片的底层数据结构中,一定会包含一个数组。数组可以被叫做切片的底层数组,而切片也可以被看做是对数组的某个连续片段的引用。

在这种情况下,切片的容量实际上代表了它的底层数组的长度,这里是8。

可以这样想:有一个窗口,你可以通过这个窗口看到一个数组,但是不一定能看到该数组中的所有元素,有时候只能看到连续的一部分元素。
image.png

现在,这个数组就是切片s2的底层数组,而这个窗口就是切片s2本身。s2的长度实际上指明的就是这个窗口的宽度,决定了你透过s2,可以看到其底层数组中的哪几个连续的元素。

由于s2的长度是5,所以你可以看到底层数组中的第 1 个元素到第 5 个元素,对应的底层数组的索引范围是 [0, 4]。

切片代表的窗口也会被划分成一个一个的小格子,就像我们家里的窗户那样。每个小格子都对应着其底层数组中的某一个元素。

我们继续拿s2为例,这个窗口最左边的那个小格子对应的正好是其底层数组中的第一个元素,即索引为0的那个元素。因此可以说,s2中的索引从0到4所指向的元素恰恰就是其底层数组中索引从0到4代表的那 5 个元素。

请记住,当我们用make函数或切片值字面量(比如[]int{1, 2, 3})初始化一个切片时,该窗口最左边的那个小格子总是会对应其底层数组中的第 1 个元素。

但是当我们通过切片表达式基于某个数组或切片生成新切片的时候,情况就变得复杂起来了。

s3 := []int{1, 2, 3, 4, 5, 6, 7, 8}
s4 := s3[3:6]
fmt.Printf("The length of s4: %d\n", len(s4))
fmt.Printf("The capacity of s4: %d\n", cap(s4))
fmt.Printf("The value of s4: %d\n", s4)

切片s3中有 8 个元素,分别是从1到8的整数。s3的长度和容量都是8。然后,我用切片表达式s3[3:6]初始化了切片s4。问题是,这个s4的长度和容量分别是多少?

这并不难,用减法就可以搞定。首先你要知道,切片表达式中的方括号里的那两个整数都代表什么。我换一种表达方式你也许就清楚了,即:[3, 6)。

这是数学中的区间表示法,常用于表示取值范围。由此可知,[3:6]要表达的就是透过新窗口能看到的s3中元素的索引范围是从3到5(注意,不包括6)。

这里的3可被称为起始索引,6可被称为结束索引。那么s4的长度就是6减去3,即3。因此可以说,s4中的索引从0到2指向的元素对应的是s3及其底层数组中索引从3到5的那 3 个元素。

image.png

前面提到过,切片的容量代表了它的底层数组的长度,但这仅限于使用make函数或者切片值字面量初始化切片的情况。

更通用的规则是:一个切片的容量可以被看作是透过这个窗口最多可以看到的底层数组中元素的个数。

由于s4是通过在s3上施加切片操作得来的,所以s3的底层数组就是s4的底层数组。

又因为,在底层数组不变的情况下,切片代表的窗口可以向右扩展,直至其底层数组的末尾。

所以,s4的容量就是其底层数组的长度8,减去上述切片表达式中的那个起始索引3,即5。

注意,切片代表的窗口是无法向左扩展的。也就是说,我们永远无法透过s4看到s3中最左边的那3个元素。

最后,顺便提一下把切片的窗口向右扩展到最大的方法。对于s4来说,切片表达式s4[0:cap(s4)]就可以做到。我想你应该能看懂。该表达式的结果值(即一个新的切片)会是[]int{4, 5, 6, 7, 8},其长度和容量都是5。

怎样估算切片容量的增长?

一旦一个切片无法容纳更多的元素,Go 语言就会想办法扩容。但它并不会改变原来的切片,而是会生成一个容量更大的切片,然后将把原有的元素和新元素一并拷贝到新切片中。在一般的情况下,你可以简单地认为新切片的容量(以下简称新容量)将会是原切片容量(以下简称原容量)的 2 倍

但是,当原切片的长度(以下简称原长度)大于或等于1024时,Go 语言将会以原容量的1.25倍作为新容量的基准(以下新容量基准)。新容量基准会被调整(不断地与1.25相乘),直到结果不小于原长度与要追加的元素数量之和(以下简称新长度)。最终,新容量往往会比新长度大一些,当然,相等也是可能的。

另外,如果我们一次追加的元素过多,以至于使新长度比原容量的 2 倍还要大,那么新容量就会以新长度为基准。注意,与前面那种情况一样,最终的新容量在很多时候都要比新容量基准更大一些。更多细节可参见runtime包中 slice.go 文件里的growslice及相关函数的具体实现。

切片的底层数组什么时候会被替换?

确切地说,一个切片的底层数组永远不会被替换。为什么?虽然在扩容的时候 Go 语言一定会生成新的底层数组,但是它也同时生成了新的切片。

它只是把新的切片作为了新底层数组的窗口,而没有对原切片,及其底层数组做任何改动。
请记住,在无需扩容时,append函数返回的是指向原底层数组的新切片,而在需要扩容时,append函数返回的是指向新底层数组的新切片。所以,严格来讲,“扩容”这个词用在这里虽然形象但并不合适。不过鉴于这种称呼已经用得很广泛了,我们也没必要另找新词了。

只要新长度不会超过切片的原容量,那么使用append函数对其追加元素的时候就不会引起扩容。这只会使紧邻切片窗口右边的(底层数组中的)元素被新的元素替换掉。

文章学习自郝林老师的《Go语言36讲》

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入探索JavaEE单体架构、微服务架构与云原生架构

课程链接: 链接: https://pan.baidu.com/s/1xSI1ofwYXfqOchfwszCZnA?pwd4s99 提取码: 4s99 复制这段内容后打开百度网盘手机App,操作更方便哦 --来自百度网盘超级会员v4的分享 课程介绍: 🔍【00】模块零:开营直播&a…

ARM-M0内核MCU,内置24bit ADC,采样率4KSPS,传感器、电子秤、体脂秤专用,国产IC

ARM-M0内核MCU 内置24bit ADC ,采样率4KSPS flash 64KB,SRAM 32KB 适用于传感器,电子秤,体脂秤等等

[BitSail] Connector开发详解系列三:SourceReader

更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群 Source Connector 本文将主要介绍负责数据读取的组件SourceReader: SourceReader 每个SourceReader都在独立的线程中执行,只要我们保证Sou…

Jmeter进阶使用:BeanShell实现接口前置和后置操作

一、背景 我们使用Jmeter做压力测试或者接口测试时,除了最简单的直接对接口发起请求,很多时候需要对接口进行一些前置操作:比如提前生成测试数据,以及一些后置操作:比如提取接口响应内容中的某个字段的值。举个最常用…

c语言——拷贝数组

这段代码是一个简单的数组拷贝示例。它的功能是将一个原始数组 original 的内容拷贝到另一个数组 copied 中,并输出两个数组的元素。 代码执行过程如下: 首先,在 main() 函数中定义了一个整型数组 original,并初始化了它的元素。…

物联网在制造业中的应用

制造业目前正在经历第四次工业革命,物联网、人工智能和机器人等技术进步正在推动行业的发展。研究表明,到2024年,全球制造商将在物联网解决方案上投资700亿美元,许多制造商正在实施物联网设备,以利用预测性维护和复杂的…

接口测试工具——Postman测试工具 Swagger接口测试+SpringBoot整合 JMeter高并发测试工具

目录 Postman测试工具接口测试工具swaggerKnife4j1.引入依赖2.配置3.常用注解4.接口测试 JMeter什么是JMeter?JMeter安装配置1.官网下载2.下载后解压3.汉语设置 JMeter的使用方法1.新建线程组2.设置参数3.添加取样器4.设置参数:协议,ip,端口…

SDK是什么,SDK和API有什么区别

SDK(Software Development Kit)是一种开发工具包,通常由软件开发公司或平台提供,用于帮助开发人员构建、测试和集成特定平台或软件的应用程序。SDK 包含一系列的库、工具、示例代码和文档,旨在简化开发过程并提供所需的…

基于Mysql+Vue+Django的协同过滤和内容推荐算法的智能音乐推荐系统——深度学习算法应用(含全部工程源码)+数据集

目录 前言总体设计系统整体结构图系统流程图 运行环境Python 环境MySQL环境VUE环境 模块实现1. 数据请求和储存2. 数据处理计算歌曲、歌手、用户相似度计算用户推荐集 3. 数据存储与后台4. 数据展示 系统测试工程源代码下载其它资料下载 前言 本项目以丰富的网易云音乐数据为基…

SQLSERVER 查询语句加with (NOLOCK) 报ORDER BY 报错 除非另外还指定了 TOP、OFFSET 或 FOR XML

最近有一个项目在客户使用时发现死锁问题,用的数据库是SQLSERVER ,死锁的原因是有的客户经常去点报表,报表查询时间又慢,然后又有人在做单导致了死锁,然后主管要我们用SQLSERVER查询时要加with (NOLOCK),但是我在加完 …

2023骨传导耳机推荐,适合运动骨传导耳机推荐

相信很多人跟我一样,随着现在五花八门的耳机品种增多,选耳机的时候真是眼花缭乱,尤其还是网购,只能看,不能试,所以选择起来比较困难, 作为一个运动达人,为了让大家在购买耳机时少走弯…

〔012〕Stable Diffusion 之 中文提示词自动翻译插件 篇

✨ 目录 🎈 翻译插件🎈 下载谷歌翻译🎈 谷歌翻译使用方法🎈 谷歌翻译使用效果 🎈 翻译插件 在插件列表中搜索 Prompt Translator可以看到有2个插件选项:一个是基于谷歌翻译 〔推荐〕、一个基于百度和deepl…

奥威BI财务数据分析方案:借BI之利,成就智能财务分析

随着智能技术的发展,各行各业都走上借助智能技术高效运作道路,财务数据分析也不例外。借助BI商业智能技术能够让财务数据分析更高效、便捷、直观立体,也更有助于发挥财务数据分析作为企业经营管理健康晴雨表的作用。随着BI财务数据分析经验的…

【RP2040】香瓜树莓派RP2040之新建工程

本文最后修改时间:2022年09月05日 11:02 一、本节简介 本节介绍如何新建一个自己的工程。 二、实验平台 1、硬件平台 1)树莓派pico开发板 ①树莓派pico开发板*2 ②micro usb数据线*2 2)电脑 2、软件平台 1)VS CODE 三、版…

【C++】一文带你初识C++继承

食用指南:本文在有C基础的情况下食用更佳 🍀本文前置知识: C类 ♈️今日夜电波:napori—Vaundy 1:21 ━━━━━━️💟──────── 3:23 …

CSS中的calc()函数有什么作用?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ CSS中的calc()函数及其作用⭐ 作用⭐ 示例1. 动态计算宽度:2. 响应式布局:3. 自适应字体大小:4. 计算间距: ⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 记得点…

KCC@广州开源读书会广州开源建设讨论会

亲爱的开源读书会朋友们, 在下个周末我们将举办一场令人激动的线下读书会,探讨两本引人入胜的新书《只是为了好玩》和《开源之迷》。作为一个致力于推广开源精神和技术创新的社区,这次我们还邀请了圈内大咖前来参与,会给大家提供一…

[UE4][C++]使用qrencode动态生成二维码

一、使用CMake编译x64版本qrencode 下载地址 GitHub - fukuchi/libqrencode: A fast and compact QR Code encoding libraryA fast and compact QR Code encoding library. Contribute to fukuchi/libqrencode development by creating an account on GitHub.https://github.…

2023/08/13_______JVM(CG)垃圾回收 算法(复制算法,标记清除,标记清除压缩)

JVM GC算法 复制算法 1,每一次GC都会将伊甸(Eden)活的对象移到幸存区中:一旦Eden区被GC后 就会是空 只要有内容就是from区 谁空谁是to区 内存会从 伊甸->幸存区to->幸存from(这个时候to和from交换区域&#xf…

EXPLAIN使用分析

系列文章目录 文章目录 系列文章目录一、type说明二、MySQL中使用Show Profile1.查看当前profiling配置2.在会话级别修改profiling配置3.查看profile记录4.要深入查看某条查询执行时间的分布 一、type说明 我们只需要注意一个最重要的type 的信息很明显的提现是否用到索引&…