深入理解go语言中的切片

写在文章开头

从一个Java的开发角度来看,切片我们可以理解为Java中的ArrayList即一种动态数组的实现,本文会从源码的角度对切片进行深入剖析,希望对你有帮助。

在这里插入图片描述

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

在这里插入图片描述

详解go语言切片

切片的底层结构

我们从切片的实现类slice.go中可以看到其结构体,其本质是通过一个指针array 指向数组,然后用len记录当前使用的长度以及数组的对应容量:

type slice struct {array unsafe.Pointerlen   intcap   int
}

就像下面这张图一样,这就是一个len为3,而cap即容量为5的切片的样子。

在这里插入图片描述

字面量创建切片

创建切片的方式分别又字面量和常量两种方式创建,字面量创建切片:

	s := []int{1, 2, 3, 4, 5}

然后运行下面这段命令查看生成的汇编码:

go build -gcflags -S main.go

这样一段简单的代码,执行了3个步骤:

  1. 创建长度为5的数组的指针。
  2. 通过newobject构建切片对象。
  3. 分配lencap两个变量的地址空间,这两个变量分别记录当前切片已使用长度和总容量。
		# 创建一个数组指针0x0018 00024 (F:\github\test\main.go:7) LEAQ    type:[5]int(SB), AX0x001f 00031 (F:\github\test\main.go:7) PCDATA  $1, $00x001f 00031 (F:\github\test\main.go:7) NOP# 构建切片对象0x0020 00032 (F:\github\test\main.go:7) CALL    runtime.newobject(SB)0x0025 00037 (F:\github\test\main.go:7) MOVQ    $1, (AX)# 分配len和cap的空间地址0x002c 00044 (F:\github\test\main.go:7) MOVQ    $2, 8(AX)0x0034 00052 (F:\github\test\main.go:7) MOVQ    $3, 16(AX)

make语法创建切片

我们也可以用make创建一个切片,如下所示,这就是创建一个长度为1且容量为1的切片:

	// 创建一个长度为5,容量为5的整数切片slice := make([]int, 1, 1)slice = append(slice, 1)fmt.Println(slice)

通过指令查看查看汇编码,可以看到使用make创建切片本质是调用了makeslice方法:

 0x0018 00024 (F:\github\test\main.go:7) LEAQ    type:int(SB), AX
0x001f 00031 (F:\github\test\main.go:7) MOVL    $10, BX
0x0024 00036 (F:\github\test\main.go:7) MOVQ    BX, CX
0x0027 00039 (F:\github\test\main.go:7) PCDATA  $1, $0
0x0027 00039 (F:\github\test\main.go:7) CALL    runtime.makeslice(SB)

这里我们也给出slice.gomakeslice方法的基本框架:

func makeslice(et *_type, len, cap int) unsafe.Pointer {mem, overflow := math.MulUintptr(et.size, uintptr(cap))//.......return mallocgc(mem, et, true)
}

切片的扩容机制

都说切片会动态扩容,这里我们创建一个容量为10的切片,在容量以内添加元素,其容量和size都没有变化,一旦追加元素就会触发扩容,可以看到此时已用size为11,容量扩容为原来的1倍,最终容量变为20:

func main() {//len和cap都为10slice := make([]int, 10)fmt.Println(len(slice))fmt.Println(cap(slice))//len和cap都为10slice[0] = 1fmt.Println(len(slice))fmt.Println(cap(slice))//追加一个 len为11 cap为20(两倍扩容)s1 := append(slice, 11)fmt.Println(len(s1))fmt.Println(cap(s1))}

我们通过slice.go定位到了扩容的方法growslice即看到扩容的代码,常规情况下如果追加后的长度大于原有容量的2倍,那么lencap都为新长度:

在这里插入图片描述

反之若追加后的元素小于容量的2倍,则直接进行2倍扩容即可:

在这里插入图片描述

若新的长度大于容量的2倍则判断旧的容量是否超256,则会按照累加(256*3+(大于256的新长度))/4得到一个值,这一点我们不妨使用极限思维来分析,假设我们追加后的长度为257,按照这个公式我们得出:

1. 旧的容量为256
2. 追加后的长度为257
3. 由公式得新容量等于(256*3+257)/4即找到区域256的最大值,最终得到256.25+旧容量256取整后为512
4. 基于此计算方式我们不断进行推算,得到10w扩容为125192无限趋近于1.25倍,由此可以得出大于256后的扩容会由2倍扩容逐步转为1.25倍扩容的稳定过度。。

这一点我们也可以从growslice的源码中得以印证:

func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice {//......//默认新容量为oldCap即旧的容量值newcap := oldCap//获取当前容量的2倍doublecap := newcap + newcap//如果添加新元素后的len大于当前容量2倍则取newLenif newLen > doublecap {newcap = newLen} else {const threshold = 256//旧容量小于256则两倍扩容if oldCap < threshold {newcap = doublecap} else {//通过该公式实现随着容量不断递增,扩容逐渐递减,得到一个从2倍到1.25倍扩容的一个过渡for 0 < newcap && newcap < newLen {newcap += (newcap + 3*threshold) / 4}if newcap <= 0 {newcap = newLen}}}//.......
}

小结

本文通过切片的创建结合汇编码了解的切片底层数据结构和创建过程,再通过代码示例结合源码的方式了解了切片的动态扩容机制,了解切片在扩容时如何在空间和时间上实现折中,希望对你有帮助。

我是 sharkchiliCSDN Java 领域博客专家开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

在这里插入图片描述

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

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

相关文章

Transformer架构实现一

从0-1搭建Transformer架构 架构图 本文主要讲解 1&#xff09;输入层的词嵌入 2&#xff09;输入层的位置编码 3&#xff09;编码层的多头注意力机制 4&#xff09;编码层的前馈全连接 1&#xff09;输入层的词嵌入 class Embeddings(nn.Module):"""构建emb…

a == 1 a== 2 a== 3 返回 true ?

1. 前言 下面这道题是 阿里、百度、腾讯 三个大厂都出过的面试题&#xff0c;一个前端同事跳槽面试也被问了这道题 // &#xff1f; 位置应该怎么写&#xff0c;才能输出 trueconst a ?console.log(a 1 && a 2 && a 3) 看了大厂的面试题会对面试官的精神…

git操作基本命令

Git命令操作&#xff1a; 1、服务器上面有新的修改&#xff0c;pull出现错误操作如下 git stash git pull origin master git stash pop 2、删除本地一个文件test.py,想重新download远程服务器最新的文件 #git checkout test.py 3、查看当前处于哪一个分支 #git …

数码相框-显示JPG图片

LCD控制器会将LCD上的屏幕数据映射在相应的显存位置上。 通过libjpeg把jpg图片解压出来RGB原始数据。 libjpeg是使用c语言实现的读写jpeg文件的库。 使用libjpeg的应用程序是以"scanline"为单位进行图像处理的。 libjpeg解压图片的步骤&#xff1a; libjpeg的使…

【御控物联】物联网平台设备接入-JSON数据格式转化(场景案例四)

文章目录 一、背景二、解决方案三、在线转换工具四、技术资料 一、背景 物联网平台是一种实现设备接入、设备监控、设备管理、数据存储、消息多源转发和数据分析等能力的一体化平台。南向支持连接海量异构&#xff08;协议多样&#xff09;设备&#xff0c;实现设备数据云端存…

前端开发攻略---在输入框中输入中文但是还没选中的时候,搜索事件依然存在;中文输入法导致的高频事件。

1、演示 解决前 解决后 2、输入框事件介绍 compositionstart事件在用户开始使用输入法输入时触发。这意味着用户正在进行组合输入&#xff0c;比如在中文输入法中&#xff0c;用户可能正在输入一个多个字符的词语。在这个阶段&#xff0c;输入框的内容可能还没有完全确定&#…

RocketMQ 10 面试题FAQ

RocketMQ 面试FAQ 说说你们公司线上生产环境用的是什么消息中间件? 为什么要使用MQ&#xff1f; 因为项目比较大&#xff0c;做了分布式系统&#xff0c;所有远程服务调用请求都是同步执行经常出问题&#xff0c;所以引入了mq 解耦 系统耦合度降低&#xff0c;没有强依赖…

Testng测试框架(2)-测试用例@Test

测试方法用 Test 进行注释&#xff0c;将类或方法标记为测试的一部分。 Test() public void aFastTest() {System.out.println("Fast test"); }import org.testng.annotations.Test;public class TestExample {Test(description "测试用例1")public void…

如何通过Python向PDF添加文本水印_python给pdf文件加文字水印

先自我介绍一下&#xff0c;小编浙江大学毕业&#xff0c;去过华为、字节跳动等大厂&#xff0c;目前阿里P7 深知大多数程序员&#xff0c;想要提升技能&#xff0c;往往是自己摸索成长&#xff0c;但自己不成体系的自学效果低效又漫长&#xff0c;而且极易碰到天花板技术停滞…

频率传感器信号采集隔离转换模拟信号0-1KHz/0-5KHz/0-10KH转0-2.5V/0-5V/0-10V/0-10mA/0-20mA/4-20mA

主要特性: >> 精度等级&#xff1a;0.2 级 >> 全量程内极高的线性度&#xff08;非线性度<0.1%&#xff09; >> 辅助电源/信号输入/信号输出&#xff1a; 2500VDC 三隔离 >> 辅助电源&#xff1a;5VDC&#xff0c;12VDC&#xff0c;24VDC 等单…

Redis Desktop Manager 中文--强大的Redis数据库管理工具

Redis Desktop Manager&#xff08;简称RDM&#xff09;是一款开源且功能强大的图形化Redis管理工具。它支持Windows、macOS和Linux等多平台&#xff0c;为Redis数据库提供了直观友好的管理界面。通过RDM&#xff0c;用户可以轻松连接多个Redis服务器&#xff0c;管理连接信息&…

【自媒体创作利器】AI白日梦+ChatGPT 三分钟生成爆款短视频

AI白日梦https://brmgo.com/signup?codey5no6idev 引言 随着人工智能&#xff08;AI&#xff09;技术的快速发展&#xff0c;AI在各个领域都展现出了强大的应用潜力。其中&#xff0c;自然语言处理技术的进步使得智能对话系统得以实现&#xff0c;而ChatGPT作为其中的代表之一…

MyBatis操作数据库(3)

其它查询操作 #{}和${} MyBatis参数赋值有两种方式, 咱们前面使用了#{}进行赋值, 接下来来看两者的区别: #{}和${}的使用 1.先看Integer类型的参数: Select("select username, password, age, gender, phone from userinfo where id #{id}") UserInfo queryByI…

攻防世界---easyRE1

1.下载附件&#xff0c;打开后有两个文件 2.对32查壳 3.对64查壳 4.IDA分析&#xff0c;这里打开之后找到main函数点击main函数后按f5 5.看到了flag----拿去提交发现是对的&#xff0c;这道题是逆向中最简单的一道了 flag{db2f62a36a018bce28e46d976e3f9864}

LeetCode501:二叉搜索树中的众数

给你一个含重复值的二叉搜索树&#xff08;BST&#xff09;的根节点 root &#xff0c;找出并返回 BST 中的所有 众数&#xff08;即&#xff0c;出现频率最高的元素&#xff09;。 如果树中有不止一个众数&#xff0c;可以按 任意顺序 返回。 假定 BST 满足如下定义&#xf…

STL —— priority_queue

博主首页&#xff1a; 有趣的中国人 专栏首页&#xff1a; C专栏 本篇文章主要讲解 priority_queue 的相关内容 目录 1. 优先级队列简介 基本操作 2. 模拟实现 2.1 入队操作 2.2 出队操作 2.3 访问队列顶部元素 2.4 判断优先队列是否为空 2.5 获取优先队列的大小 …

什么是One-Class SVM

1. 简介 单类支持向量机&#xff0c;简称One-Class SVM(One-Class Support Vector Machine)&#xff0c;用于异常检测和离群点检测(无监督学习&#xff0c;其他svm属于有监督的)&#xff0c;可以在没有大量异常样本的情况下有效地检测异常。其目标是通过仅使用正常数据来建模&a…

【力扣 Hot100 | 第四天】4.15(括号生成)

文章目录 4.括号生成4.1题目4.2解法&#xff1a;回溯4.2.1回溯思路&#xff08;1&#xff09;函数返回值以及参数&#xff08;2&#xff09;终止条件&#xff08;3&#xff09;遍历过程 4.2.2代码 4.括号生成 4.1题目 数字 n 代表生成括号的对数&#xff0c;请你设计一个函数…

三斜求积术 To 海伦公式 ← 三角形面积

【知识点&#xff1a;三斜求积术】 所谓秦九韶的三斜求积术&#xff0c;即如果已知三角形的边长a&#xff0c;b&#xff0c;c&#xff0c;可求得该三角形的面积为&#xff1a; 而由三斜求积术可推得海伦公式。过程如下&#xff1a; 其中&#xff0c; 上面推导公式的 Latex 代码…

​​​​网络编程探索系列之——广播原理剖析

hello &#xff01;大家好呀&#xff01; 欢迎大家来到我的网络编程系列之广播原理剖析&#xff0c;在这篇文章中&#xff0c; 你将会学习到如何在网络编程中利用广播来与局域网内加入某个特定广播组的主机&#xff01; 希望这篇文章能对你有所帮助&#xff0c;大家要是觉得我写…